{"openapi":"3.1.1","info":{"title":"OrbSearch","version":"0.1.0","license":{"name":"MIT","identifier":"MIT"}},"servers":[{"url":"http://127.0.0.1:7800","description":"The Query API"}],"paths":{"/health":{"get":{"operationId":"health","summary":"Whether the Query API is up","description":"Answers while the server runs; the container's health check asks it.","responses":{"200":{"description":"The server runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/v1/search":{"post":{"operationId":"search","summary":"Search, browse a category or complete a query","description":"Answers one request: sections of results or a redirect, named by the release and the catalog snapshot that answered it. The time is set when the request arrives; a request that brings its own `time` is refused. With `debug`, the response carries the trace: one entry per step.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"The answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"The request cannot be answered as sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"operationId":"searchByUrl","summary":"Search with the request in the URL","description":"The same search with the request's plain fields as URL parameters, so a link or `curl` is enough: `/v1/search?query=sofa&debug=1`. Filters and context need the request body of `POST /v1/search`.","parameters":[{"name":"query","in":"query","schema":{"type":"string","default":""},"description":"What the shopper typed."},{"name":"on","in":"query","schema":{"$ref":"#/components/schemas/Surface"},"description":"Where the request comes from. Default: `search`."},{"name":"channel","in":"query","schema":{"$ref":"#/components/schemas/ChannelId"}},{"name":"locale","in":"query","schema":{"$ref":"#/components/schemas/Locale"}},{"name":"category","in":"query","schema":{"$ref":"#/components/schemas/CategoryId"},"description":"The category page being browsed."},{"name":"sort","in":"query","schema":{"$ref":"#/components/schemas/SortCode"}},{"name":"page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1},"description":"The page, counted from 1."},{"name":"per_page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1,"maximum":100},"description":"Hits per page, up to the limit `per_page`. Default: 24."},{"name":"count_only","in":"query","schema":{"type":"boolean","default":false},"description":"`1` or `true` answers with the totals alone."},{"name":"redirect","in":"query","schema":{"type":"boolean","default":true},"description":"`0` or `false` answers with results where the response would send the shopper elsewhere."},{"name":"debug","in":"query","schema":{"type":"boolean","default":false},"description":"`1` or `true` adds the trace to the response."},{"name":"page_view","in":"query","schema":{"$ref":"#/components/schemas/PageViewId"},"description":"The page the shopper loaded, as in the request body."},{"name":"traffic","in":"query","schema":{"$ref":"#/components/schemas/Traffic"},"description":"Whose search this is. Default: `shopper`."}],"responses":{"200":{"description":"The answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"The request cannot be answered as sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/product":{"get":{"operationId":"product","summary":"One product as its page shows it","description":"Answers one product by its id or by its URL in the locale: its text, photos and categories, the details the type lists with their labels and units, its variants, and its offer as its tile shows it, all in the request's channel and locale and named by the release and the catalog snapshot that answered. With `debug`, the response carries the trace.","parameters":[{"name":"id","in":"query","schema":{"$ref":"#/components/schemas/EntityId"},"description":"The merchant's id of the product."},{"name":"url","in":"query","schema":{"type":"string"},"description":"The product's URL in the locale as the catalog writes it, such as `/p/aska-2-sitzer-sofa`, instead of its id."},{"name":"channel","in":"query","schema":{"$ref":"#/components/schemas/ChannelId"},"description":"The channel. Default: the release's first channel."},{"name":"locale","in":"query","schema":{"$ref":"#/components/schemas/Locale"},"description":"The locale. Default: the channel's first locale."},{"name":"debug","in":"query","schema":{"type":"boolean","default":false},"description":"`1` or `true` adds the trace to the response."}],"responses":{"200":{"description":"The product.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductResponse"}}}},"400":{"description":"The request names no product, or both an id and a URL, or a channel or locale the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The channel sells no product by this id or URL in the locale.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/resolve":{"get":{"operationId":"resolve","summary":"What page one of the merchant's URLs is","description":"Answers what one of the merchant's URLs is, in the order normalize, legacy table, search page, category and brand paths, product URL: a listing with its search and results in one call, a product, a redirect to the final URL, a URL gone on purpose, or not found. The page carries the status, canonical and robots a storefront answers with; the call itself answers 200 for every page. With `debug`, the response carries the trace: each step of the order and what it found.","parameters":[{"name":"url","in":"query","schema":{"type":"string"},"description":"The URL the shopper or crawler asked for: a path with its query string, such as `/wohnen/sofas?farbe=grau`,\nor a whole URL, whose scheme and host are ignored."},{"name":"channel","in":"query","schema":{"$ref":"#/components/schemas/ChannelId"},"description":"The channel. Default: the release's first channel."},{"name":"locale","in":"query","schema":{"$ref":"#/components/schemas/Locale"},"description":"The locale. Default: the channel's first locale."},{"name":"results","in":"query","schema":{"type":"boolean","default":true},"description":"`0` or `false` answers with the decision alone, without the listing's results or the product, as URL tests and\nagents need. Default: `1`."},{"name":"per_page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1,"maximum":100},"description":"Hits per page of a listing, up to the limit `per_page`. Default: 24."},{"name":"debug","in":"query","schema":{"type":"boolean","default":false},"description":"`1` or `true` adds the trace to the response."},{"name":"page_view","in":"query","schema":{"$ref":"#/components/schemas/PageViewId"},"description":"The page the shopper loaded, which the storefront names anew on every page load, as in a search request."},{"name":"traffic","in":"query","schema":{"$ref":"#/components/schemas/Traffic"},"description":"Whose request this is. Default: `shopper`."}],"responses":{"200":{"description":"The page, whatever its own status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageResponse"}}}},"400":{"description":"The request names a channel or locale the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/robots":{"get":{"operationId":"robots","summary":"The robots.txt lines that keep crawlers off filter states","description":"The `Disallow` lines the merchant serves in their own `robots.txt`, written from `routes.json`: one for every parameter no indexable page uses, one for any URL with a second parameter, and one for the search page.","responses":{"200":{"description":"The lines.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RobotsLines"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/events":{"post":{"operationId":"events","summary":"Report clicks on and views of an answer's hits and banners","description":"Takes a batch of clicks and views from one page load, each naming the query ID of the answer it belongs to, and hands it to the search log without waiting for it, as a search is: Insights joins each to its search at the next roll-up. A browser sends it with `navigator.sendBeacon`, which posts it as `text/plain`, so no preflight precedes it; it carries no cookie and the answer sets none. An event whose answer is older than `LIMITS.event_hours` is refused by its query ID's time alone and counted as late; the others are accepted. Events marked as test or bench traffic, or sent by a browser the bot list names, are left out of the reports as their searches are.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Events"}},"text/plain":{"schema":{"$ref":"#/components/schemas/Events"}}}},"responses":{"202":{"description":"What it took; the events show after the next roll-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsTaken"}}}},"400":{"description":"The body is not a batch of events: no events or more than `LIMITS.events_per_call`, an event naming both or neither of an entity and a placement, a position no page reaches, or a query ID that is not a UUIDv7 or names a time still to come. Nothing of it is taken.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than the 64 kB a beacon may carry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/releases/validate":{"post":{"operationId":"validateRelease","summary":"Check a release and write its files canonically","description":"For the control plane only. Reads a release from its files by name and checks it as a whole. A valid release comes back as its id and its files in canonical form, which is what the control plane stores; an invalid one as a problem whose violations name file, JSON pointer and sentence.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReleaseFiles"}}}},"responses":{"200":{"description":"The release is valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidatedRelease"}}}},"400":{"description":"The body is not a release's files by name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The release has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/imports/inspect":{"post":{"operationId":"inspectExport","summary":"Show what the import makes of a kept export","description":"For the control plane only. Reads a snapshot from the shared storage with the release its files make and answers what the indexer would make of it, with the indexer's own steps; nothing goes live. A sample maps the first rows only, so the panel can redraw while a merchant corrects the mapping; a file of up to 8 MB is read whole, which takes about as long.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportToInspect"}}}},"responses":{"200":{"description":"The inspection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Inspection"}}}},"400":{"description":"The body is not an export to inspect.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The storage holds no such snapshot.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The release has violations, or the export cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/imports/suggest":{"post":{"operationId":"suggestExportMapping","summary":"Ask a decision model where a kept export's columns go","description":"For the control plane only. Inspects the export as `inspectExport` does, then shows the decision model behind the AI gateway every column that holds values with its first values, and asks which column holds each field one column fills, such as the price, and what each other column holds: an attribute or signal of the release, more photos, search words, a property of its own, or nothing shoppers see. The answer is a suggestion per column with the model's probability, and the mapping with the near-certain ones taken. Off while the server has no `AI_GATEWAY_API_KEY`.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportToInspect"}}}},"responses":{"200":{"description":"The suggestions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingSuggestions"}}}},"400":{"description":"The body is not an export to inspect.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The storage holds no such snapshot.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The release has violations, or the export cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The AI gateway did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Mapping suggestions are off: the server has no AI gateway key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/imports/compare":{"post":{"operationId":"compareExports","summary":"Compare a kept export with the live one","description":"For the control plane only. Reads both snapshots from the shared storage, each with the release its files make, and answers what making the draft live changes: products added and removed, attributes and filters new and gone. Without `live`, everything is new.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportsToCompare"}}}},"responses":{"200":{"description":"The changes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportChanges"}}}},"400":{"description":"The body is not two exports to compare.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The storage holds no such snapshot.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A release has violations, or an export cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/publication":{"post":{"operationId":"showPublicationForControl","summary":"Show how publishing a release with a catalog would go live","description":"For the control plane only. Decides how a release with a catalog would go live, by the decision the indexer takes when it claims the run: a switch, after settings the engine applies without reindexing, or by a rebuild of the indexes, and why. It compares what the compile reads of the release and the catalog with what the live run was built from, derives the pages the release leaves out from what the live run recorded of its catalog, and asks the engine whether the live indexes are still there. For a rebuild it estimates the time from the last rebuild. Nothing is published.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicationRequest"}}}},"responses":{"200":{"description":"How it would go live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Publication"}}}},"400":{"description":"The body is not a catalog and a release's files.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The release has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/categories":{"get":{"operationId":"listCategoriesForControl","summary":"Show the category tree of what is live","description":"For the control plane only. Every category of the live catalog with its parent, titles, paths and channels, and the type its page lists with how many entities that is, named by the release and the catalog snapshot that answered. The nodes name their parents, so the caller arranges the tree.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"responses":{"200":{"description":"The categories.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryTree"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/pages/settings":{"post":{"operationId":"showPageSettingsForControl","summary":"Show what a page shows and where each setting comes from","description":"For the control plane only. The settings a search on a category page or the search page reads: its facets in display order with their groups, the order it starts in and the orders it offers, each with where it comes from (the page, the nearest category above it that sets it, the default layout, or nothing) and whether the merchant wrote it or the index run derived it, with the run's reason; labels in the locale, and the page's address. With `files`, they are read from a draft's files over the live release, completed as a run completes them.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageSettingsRequest"}}}},"responses":{"200":{"description":"The page's settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageSettings"}}}},"400":{"description":"The body names a category the live catalog lacks, or a channel or locale the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/rules":{"post":{"operationId":"listRulesForControl","summary":"List the rules in order, each with its state and its conflicts","description":"For the control plane only. Every rule of the live release, or of the draft the body's `files` make over it, in rule order. Each has what it does in one line, its state at `time` (active, scheduled, expired, disabled, or dormant), the conflicts with the rules above it, the entities it names that the catalog no longer holds, and the fields an editor sets, or its JSON where the fields cannot express it. The catalog is asked once for the entities the rules name. States and conflicts are data with parameters and one sentence each, so no caller words them again.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RulesRequest"}}}},"responses":{"200":{"description":"The rules.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleList"}}}},"400":{"description":"The body names a channel or locale the release lacks, or cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/rules/edit":{"post":{"operationId":"editRulesForControl","summary":"Create, change, move or delete one rule of a rules.json","description":"For the control plane only. Applies one change to the text of a `rules.json` and answers the file in canonical form: a rule made from its fields, changed, moved or deleted. Nothing is stored or published; the control plane stores the file in the draft, which checks the release as a whole.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditRules"}}}},"responses":{"200":{"description":"The file after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditedRules"}}}},"400":{"description":"The body cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The file holds no rule with this id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The file holds a rule with this id already.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The fields have violations, the rule is one the fields cannot express, or the file cannot be read; each violation names a JSON pointer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/synonyms":{"post":{"operationId":"listSynonymsForControl","summary":"List the synonyms of every language, each entry with what it clashes with","description":"For the control plane only. Every entry of the live release's `synonyms.json`, or of the one the body's `files` hold, by language: the languages the channels serve first, then those none serves yet. Each entry has its id, its words as the engine reads them, what it does in one line, and what it clashes with: a word another group holds too, or words a `never` entry keeps apart that it merges. Only the files are read; nothing is compiled or searched.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynonymsRequest"}}}},"responses":{"200":{"description":"The synonyms.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynonymList"}}}},"400":{"description":"The body cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A file of the body cannot be read; each violation names its file and a JSON pointer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/synonyms/edit":{"post":{"operationId":"editSynonymsForControl","summary":"Add, change or delete synonym entries of a synonyms.json","description":"For the control plane only. Applies one change to the text of a `synonyms.json` and answers the file in canonical form: entries added to a language, one replaced, or one deleted. Words are trimmed, lowercased and kept once. An entry that would put a word in a second group, start a second one-way entry from the same word, or merge words a `never` entry keeps apart is refused with the entry it clashes with; an add writes the others. Nothing is stored or published; the control plane stores the file in the draft, which checks the release as a whole.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSynonyms"}}}},"responses":{"200":{"description":"The file after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditedSynonyms"}}}},"400":{"description":"The body cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The file holds no entry with this id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Every entry clashes, so nothing is written, and `refused` says why for each; or the file cannot be read, or an add carries more than `LIMITS.synonyms_per_call` entries.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/SynonymsRefused"}}}}}}},"/internal/synonyms/check":{"post":{"operationId":"checkSynonymForControl","summary":"Say what an entry's words find today and what writing it would clash with","description":"For the control plane only. Says, for an entry being typed, how many results the search page lists for each of its words now, searched with the live release in one engine call, and what writing it into the synonyms of the body's `files`, or the live ones, would clash with, as an add says. Nothing is written and no draft is indexed, so the counts are those of the live synonyms.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckSynonym"}}}},"responses":{"200":{"description":"The words' counts and the clashes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynonymCheck"}}}},"400":{"description":"The body names a channel or locale the release lacks, or cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A file of the body cannot be read, or the entry has more than `LIMITS.words_per_check` words.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/search-settings":{"post":{"operationId":"showSearchSettingsForControl","summary":"Show how each type is searched, each setting with where it comes from","description":"For the control plane only. How each type of the live release, or of the release the body's `files` make over it, is searched: its fields in priority order, the fields it could search besides, whether and from how many letters on its words find with a typo, and the attributes exempt from typos, each setting marked as set in `types.json`, derived by the live index run from the catalog, or built in. A derived list of exempt attributes says why for each identifier the type searches, the ones left open to typos included. Nothing is searched.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchSettingsRequest"}}}},"responses":{"200":{"description":"The search settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchSettings"}}}},"400":{"description":"The body names a locale the release lacks, or cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The release the files make has violations; each names its file and a JSON pointer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/search-settings/edit":{"post":{"operationId":"editSearchSettingsForControl","summary":"Change how one type of a types.json is searched","description":"For the control plane only. Applies one change of one type's searched fields, typo settings or exempt attributes to the text of a `types.json` and answers the file in canonical form; `null` sets a setting back to its default. Where the text is empty, the change starts from the types the live run derived, whose exempt attributes stay derived. Nothing is stored or published; the control plane stores the file in the draft, which checks the release as a whole.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSearch"}}}},"responses":{"200":{"description":"The file after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditedSearch"}}}},"400":{"description":"The body cannot be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The file holds no type with this code.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The change has violations, such as no searched field or `two_typos` below `one_typo`, or the file cannot be read; each violation names a JSON pointer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The text is empty and nothing is live to start from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/offers/check":{"post":{"operationId":"checkOffersForControl","summary":"Check offer changes against the live catalog","description":"For the control plane only. Checks each offer change on its own against what searches read now: the product and its variant in the live catalog, the channel in the live release, and values an offer can hold. A product an overlay hides is still known. Nothing is stored; the control plane keeps the changes it accepts.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferChanges"}}}},"responses":{"200":{"description":"One verdict per change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OffersChecked"}}}},"422":{"description":"The body holds no list of offers, or more than `LIMITS.offers_per_call`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/preview/search":{"post":{"operationId":"previewSearchForControl","summary":"Search the live release in any context and at any time, with the trace","description":"For the control plane only. Answers a request as `POST /v1/search` does, against the live release, except that the request may fix its `time`, so a scheduled rule can be checked on the day it starts; without one it is answered now. The response always carries the trace. A preview is the control plane's, never a shopper's: nothing that counts searches counts it, so the panel measures through it.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"The answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"The request cannot be answered as sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/preview/page":{"post":{"operationId":"previewPageForControl","summary":"Resolve one of the merchant's URLs in any context and at any time, with the trace","description":"For the control plane only. Answers a URL as `GET /v1/resolve` does, with its results, against the live release, in the context and at the time the body fixes; without a time it is answered now. The page and its results always carry their traces. With `files`, the page is planned with them in place of the live release's own, so a draft's facets, sorting and URLs are seen before it is published. Like every preview, it is never counted as a shopper's search.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagePreview"}}}},"responses":{"200":{"description":"The page, whatever its own status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageResponse"}}}},"400":{"description":"The request names a channel or locale the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/preview/compare":{"post":{"operationId":"previewCompareForControl","summary":"Say why one hit of a trace sits above another","description":"For the control plane only. Reads two hits of the trace a preview answered with and says what put the higher one above the other, from the trace alone: a pin, boosts and buries that outweighed the text, or the first criterion they differ in.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HitsToCompare"}}}},"responses":{"200":{"description":"Why one sits above the other.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HitComparison"}}}},"400":{"description":"The body is not a trace and two places.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The trace's rank step holds no hit of the section at one of the places.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/internal/preview/find":{"post":{"operationId":"previewFindForControl","summary":"Say where one entity is for a request, or which stage kept it off the page","description":"For the control plane only. Answers the request as `previewSearchForControl` does, then walks the stages one entity passes on its way onto the page, in one more engine call: the index, the channel, the category, the rules' filters and hides, the shopper's and the query's filters, the query's words and its place among the hits. The answer names the first stage that drops it, with what it found and what to do, or its place.","tags":["internal"],"servers":[{"url":"http://127.0.0.1:7801","description":"The internal port"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityToFind"}}}},"responses":{"200":{"description":"Where it is, or the stage that kept it off the page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Finding"}}}},"400":{"description":"The request cannot be answered as sent, or the body names no entity.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/catalog":{"put":{"operationId":"uploadCatalog","summary":"Replace the catalog","description":"The body is the whole catalog as the shop exported it, in any of the media types listed. It is kept byte for byte as a snapshot named by its content; the published release's feed.json says how its columns map, and a later release reads the same snapshot again. With a release published, an index run makes it searchable, and `next` says what happens.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/x-ndjson":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}},"text/csv":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}},"text/tab-separated-values":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}},"application/xml":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}},"application/json":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}},"application/gzip":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}},"application/octet-stream":{"schema":{"description":"The shop's export as it wrote it: OrbSearch JSON Lines, Merchant Center XML, CSV or TSV, an XLSX workbook or JSON records, any of them gzipped.","type":"string","format":"binary"}}}},"responses":{"202":{"description":"The catalog is kept.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogUploaded"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than the control plane accepts.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The body holds no catalog.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/catalog-source":{"get":{"operationId":"showCatalogSource","summary":"Show the feed the catalog is fetched from, with its latest fetches","description":"The feed URL, how it signs in without its secret, its schedule, when it is fetched next, and its last ten fetches, newest first, each with how it ended and why. `needs_attention` is set while the latest changed feed is held back or the last three fetches failed.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"200":{"description":"The catalog source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSource"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"operationId":"setCatalogSource","summary":"Fetch the catalog from a feed URL on a schedule","description":"The control plane fetches the feed every `interval_minutes`, asking whether it changed since the last fetch. A feed that changed becomes an import and goes live as publishing it would, or waits as a draft when `on_change` is `draft`; one that would remove half the live products or more is held back as a draft to confirm. The same bytes again queue nothing. A new URL or new credentials are fetched at once. The credentials are kept encrypted and never answered; left out, the kept ones stay, and `null` removes them. A URL in a private, loopback or link-local network is refused at fetch time unless the stack allows it.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSourceChange"}}}},"responses":{"200":{"description":"The catalog source is changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSource"}}}},"201":{"description":"The catalog source is set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSource"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A setting is missing or out of range.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"removeCatalogSource","summary":"Stop fetching the catalog from its feed URL","description":"The source and its fetches go; the imports its fetches made stay in the history.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"200":{"description":"The source is removed, as it was.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSource"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/catalog-source/fetch":{"post":{"operationId":"fetchCatalogSource","summary":"Fetch the catalog source now","description":"The schedule starts the fetch within seconds; `showCatalogSource` lists it once it has started, and how it ended once it has.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"202":{"description":"The fetch is asked for.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSource"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports":{"get":{"operationId":"listImports","summary":"List the imports, newest first","description":"A page of imports; `links` and `meta` lead to the others. Every import keeps the release it went live with, so any one of them can go live again while its export is kept.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1},"description":"The page, counted from 1. Default: 1."},{"name":"per_page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1,"maximum":100},"description":"How many to a page. Default: 15."}],"responses":{"200":{"description":"The imports.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Imports"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"createImport","summary":"Start an import: keep an export without making it live","description":"The body is the export as the shop wrote it, in any of the media types listed, kept byte for byte as a snapshot. Nothing goes live: inspect it, correct the files it is read with, then publish it. `PUT /api/v1/catalog` is the one-step path for automation that needs no look first.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"name","in":"query","schema":{"type":"string"},"description":"The file's name, such as `export.csv`, so the panel and the history can tell imports apart."}],"requestBody":{"required":true,"content":{"application/x-ndjson":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}},"text/csv":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}},"text/tab-separated-values":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}},"application/xml":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}},"application/json":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}},"application/gzip":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}},"application/octet-stream":{"schema":{"description":"The shop's export as it wrote it, in any format `PUT /api/v1/catalog` takes.","type":"string","format":"binary"}}}},"responses":{"201":{"description":"The export is kept as a draft import.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportStarted"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than the control plane accepts.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The body holds no export.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}":{"get":{"operationId":"showImport","summary":"Show an import with its files and its run","description":"Its state says where it stands; once published, `run` says how the index run goes.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The import.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Import"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"patch":{"operationId":"changeImport","summary":"Change the release files an import is read with","description":"Each named file replaces the import's own version of it, as its JSON object or as text; `null` drops it, so the published release's file applies again. The Query API checks the release they make together. Only an import that has not been published can change. `version` is the import's version you read: a draft changed since, by a person or an agent, refuses the write with 409 and the draft as it is now, so nobody overwrites another silently.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportChange"}}}},"responses":{"200":{"description":"The files are changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Import"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"The release the files make has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not check it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"discardImport","summary":"Discard an import that has not gone live","description":"The import leaves the list of drafts and stays in the history; the indexer's clean-up removes its export.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The import is discarded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Import"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The import was published.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/inspection":{"get":{"operationId":"inspectImport","summary":"Show what the import makes of its export","description":"What the import makes of the export with its files: the format, every column with samples, where it goes and why, the products it becomes, the first of them as they would be indexed, and the rows that would be left out, grouped by cause. With `sample`, the products and problems cover the first rows only, which answers a large export faster.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"sample","in":"query","schema":{"type":"integer","format":"uint64","minimum":1},"description":"Map only the first rows, which answers faster for a large export; the columns and the mapping still describe\nthe whole file. Default: every row."}],"responses":{"200":{"description":"The inspection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Inspection"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The export cannot be read, or the release has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not inspect it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/suggestions":{"get":{"operationId":"suggestMapping","summary":"Show where a decision model would send the export's columns","description":"For each column that holds values, the field, attribute or signal a decision model behind the AI gateway chose for it, with its probability. The model is asked once per import and the answer is kept, so asking again costs nothing. A suggestion changes nothing until it is chosen: send the column's new place in `feed.json` with `changeImport`. Off while the Query API has no AI gateway key.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The suggestions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingSuggestions"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The export cannot be read, or the release has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API or the AI gateway did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Mapping suggestions are off: the Query API has no AI gateway key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/changes":{"get":{"operationId":"showImportChanges","summary":"Show what making an import live changes","description":"The import's export and files against what searches read now: the products added and removed, as each export names them, and the attributes and filters new and gone. Both exports are read whole, so a large one takes a few seconds per gigabyte. When the draft removes half the live products or more, `products.needs_confirmation` is set and publishing it needs `removing`.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The changes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportChanges"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"An export cannot be read, or the release has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not compare them.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/publish":{"post":{"operationId":"publishImport","summary":"Make an import live, or go back to an earlier one","description":"Stores the release its files make, publishes it and queues an index run with its export; `run` says how it goes. Publishing an import that was live before is how a merchant goes back to it. An import that would remove half the live products or more is published only with `removing` set to the number `showImportChanges` names, because a broken export must not empty a shop.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"removing","in":"query","schema":{"type":"integer","format":"uint64","minimum":0},"description":"How many live products the export leaves out, as `showImportChanges` counts them: the confirmation that an\nimport removing half the live products or more is meant to."},{"name":"version","in":"query","schema":{"$ref":"#/components/schemas/DraftVersion"},"description":"The draft's `version` as reviewed, so what goes live is what was looked at: a draft that changed since is\nrefused. Default: the draft as it is."}],"responses":{"202":{"description":"The import is published.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportPublished"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, or while its release was built, and `draft` is the draft as it is now; or the import was discarded, is going live already, its export was removed, or it removes half the live products or more and `removing` does not name how many, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"The release the files make has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not check it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/publication":{"get":{"operationId":"showImportPublication","summary":"Show how publishing the import would go live, before it does","description":"What publishing the import would do, decided by the decision the indexer takes: `course` says whether it goes live at once (a switch), after settings the engine applies without reindexing, or by a rebuild of the indexes, and why, as data and as a sentence. For a rebuild, `estimate_seconds` is about how long it takes, from the last rebuild; the shop keeps answering with the live release meanwhile. Nothing is published.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"How publishing would go live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Publication"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"There is no catalog yet, so nothing can be indexed before one is uploaded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/rules":{"get":{"operationId":"listRules","summary":"List the live rules in order, each with its state and its conflicts","description":"Every rule of the release searches read, in rule order. Each has what it does in one line, its state at `time` (active, scheduled, expired, disabled, or dormant because everything it acts on left the catalog), the conflicts with the rules above it, the entities it names that the catalog no longer holds, and the fields an editor sets. A rule the fields cannot express yet comes with its JSON, read only. States and conflicts are data with parameters, and a sentence that says the same, written once by the Query API.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"channel","in":"query","schema":{"$ref":"#/components/schemas/ChannelId"},"description":"The channel the catalog is read in. Default: the release's first channel."},{"name":"locale","in":"query","schema":{"$ref":"#/components/schemas/Locale"},"description":"The locale the products are named in. Default: the channel's first locale."},{"name":"time","in":"query","schema":{"$ref":"#/components/schemas/Timestamp"},"description":"The time the states are as at, so a scheduled rule can be seen on the day it starts. Default: now."}],"responses":{"200":{"description":"The rules.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleList"}}}},"400":{"description":"The channel, locale or time cannot be answered, such as a channel the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/rules":{"get":{"operationId":"listDraftRules","summary":"List a draft's rules in order, each with its state and its conflicts","description":"The same answer for the rules a draft makes, with the draft's `version` to write on. A draft that changes no rules lists the live ones.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"channel","in":"query","schema":{"$ref":"#/components/schemas/ChannelId"},"description":"The channel the catalog is read in. Default: the release's first channel."},{"name":"locale","in":"query","schema":{"$ref":"#/components/schemas/Locale"},"description":"The locale the products are named in. Default: the channel's first locale."},{"name":"time","in":"query","schema":{"$ref":"#/components/schemas/Timestamp"},"description":"The time the states are as at, so a scheduled rule can be seen on the day it starts. Default: now."}],"responses":{"200":{"description":"The draft's rules.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftRules"}}}},"400":{"description":"The channel, locale or time cannot be answered, such as a channel the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"createRule","summary":"Add a rule to a draft","description":"Adds one rule through its fields; the first place in the order unless `position` says another. The Query API makes the rule, writes `rules.json` and checks the release as for `changeImport`, so violations come back with their JSON pointers. The answer is the draft's rules after the write.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRule"}}}},"responses":{"201":{"description":"The rule is added.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftRules"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"The write has violations, or the release it makes does; each names a JSON pointer and what is wrong. A change of fields to a rule they cannot express is refused here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/rules/{rule}":{"patch":{"operationId":"changeRule","summary":"Change one rule of a draft","description":"Changes the name, whether it is on, or its fields, all of them at once. A rule the fields cannot express yet keeps its name and switch changeable, and its fields are changed through the draft's files with `changeImport`. A rule id may hold slashes, which are sent as they are: `/api/v1/imports/3/rules/home/out-of-stock-last`.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"rule","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeRule"}}}},"responses":{"200":{"description":"The draft's rules after the write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftRules"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"The write has violations, or the release it makes does; each names a JSON pointer and what is wrong. A change of fields to a rule they cannot express is refused here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"deleteRule","summary":"Remove one rule from a draft","description":"Removes the rule from the draft. Publishing the draft removes it from searches; an earlier release still holds it.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"rule","in":"path","required":true,"schema":{"type":"string"}},{"name":"version","in":"query","schema":{"$ref":"#/components/schemas/DraftVersion"},"description":"The draft's `version` as read."}],"responses":{"200":{"description":"The draft's rules after the write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftRules"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"The write has violations, or the release it makes does; each names a JSON pointer and what is wrong. A change of fields to a rule they cannot express is refused here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/rules/{rule}/position":{"put":{"operationId":"moveRule","summary":"Move one rule of a draft to another place in the order","description":"Puts the rule at `position` in the order; the rules between shift by one. Where two effects exclude each other, the higher rule wins, so the order is precedence.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"rule","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveRule"}}}},"responses":{"200":{"description":"The draft's rules after the write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftRules"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"The write has violations, or the release it makes does; each names a JSON pointer and what is wrong. A change of fields to a rule they cannot express is refused here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/synonyms":{"get":{"operationId":"listSynonyms","summary":"List the live synonyms of every language","description":"Every synonym entry of the release searches read, by language: groups of words that mean the same, one-way entries, and words never merged. Each has its id, its words as the engine reads them, what it does in one line, and what it clashes with, as data and a sentence written once by the Query API. `locales` lists the languages the channels serve, the shop's first.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"200":{"description":"The synonyms.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynonymList"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/synonyms":{"get":{"operationId":"listDraftSynonyms","summary":"List a draft's synonyms of every language","description":"The same answer for the synonyms a draft holds, with the draft's `version` to write on. A draft that changes no synonyms lists the live ones.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The draft's synonyms.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftSynonyms"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft's synonyms.json cannot be read; change it through the draft's files.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"addSynonyms","summary":"Add synonym entries to a draft, one or many at once","description":"Adds entries to one language, such as `{ \"kind\": \"group\", \"words\": [\"couch\", \"sofa\"] }`, one or up to `LIMITS.synonyms_per_call` at once, as pasting lines does. Words are trimmed, lowercased and kept once. An entry that would put a word in a second group, start a second one-way entry from the same word, or merge words a `never` entry keeps apart is refused, naming the entry it clashes with so the word can be added there; `despite_never` writes the last kind anyway. The others are written, and the answer lists both. Synonyms go live as settings, without a rebuild.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSynonyms"}}}},"responses":{"201":{"description":"Entries are added; `refused` lists those that clash.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftSynonyms"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"Nothing is written: every entry clashes, and `refused` says why for each; or the release the write makes has violations, each with a JSON pointer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/SynonymsRefused"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/synonyms/{synonym}":{"patch":{"operationId":"changeSynonym","summary":"Change one synonym entry of a draft","description":"Replaces the entry's words, checked as an add checks them. An entry id holds slashes, which are sent as they are: `/api/v1/imports/3/synonyms/de/groups/0`. An entry that changes its kind moves after the entries of its new kind, and the answer names its new id.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"synonym","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeSynonym"}}}},"responses":{"200":{"description":"The draft's synonyms after the write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftSynonyms"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"Nothing is written: every entry clashes, and `refused` says why for each; or the release the write makes has violations, each with a JSON pointer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/SynonymsRefused"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"deleteSynonym","summary":"Remove one synonym entry from a draft","description":"Removes the entry from the draft; the entries after it of its kind move up one place, so their ids change and the next write reads the list again.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"synonym","in":"path","required":true,"schema":{"type":"string"}},{"name":"version","in":"query","schema":{"$ref":"#/components/schemas/DraftVersion"},"description":"The draft's `version` as read."}],"responses":{"200":{"description":"The draft's synonyms after the write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftSynonyms"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"Nothing is written: every entry clashes, and `refused` says why for each; or the release the write makes has violations, each with a JSON pointer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/SynonymsRefused"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/synonyms/check":{"post":{"operationId":"checkSynonym","summary":"Say what an entry's words find today and what adding it to a draft would clash with","description":"For an entry being typed: how many results the search page lists for each word now, searched with the live release in one engine call, and what adding it to the draft would clash with, such as the group a word is in already. Nothing is written, and the draft is not indexed, so the counts are those of the live synonyms.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynonymToCheck"}}}},"responses":{"200":{"description":"The words' counts and the clashes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynonymCheck"}}}},"400":{"description":"The body names a channel or locale the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft's synonyms.json cannot be read, or the entry has more than `LIMITS.words_per_check` words.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/search-settings":{"get":{"operationId":"showSearchSettings","summary":"Show how each type is searched now: its fields, typo settings and exempt attributes","description":"How each type searches read is searched: its fields in priority order, where a word found in an earlier field ranks a hit higher, the fields it could search besides, whether and from how many letters on its words find with a typo, and the attributes exempt from typos, whose words are found only as written or by their start. Each setting says whether `types.json` sets it, the index run derived it from the catalog, or it is OrbSearch's default. Exempt attributes are derived from the catalog: the identifiers a type searches whose values nearly always belong to one product alone; `open` lists the identifiers left open to typos, each with why.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"locale","in":"query","schema":{"$ref":"#/components/schemas/Locale"},"description":"Default: the first channel's first locale."}],"responses":{"200":{"description":"The search settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchSettings"}}}},"400":{"description":"The locale is not one the release serves.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/search-settings":{"get":{"operationId":"showDraftSearchSettings","summary":"Show how each type of a draft is searched","description":"The same answer for the release a draft makes, with the draft's `version` to write on, its derived settings as the draft's run would derive them from the live catalog.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"locale","in":"query","schema":{"$ref":"#/components/schemas/Locale"},"description":"Default: the first channel's first locale."}],"responses":{"200":{"description":"The draft's search settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftSearchSettings"}}}},"400":{"description":"The locale is not one the release serves.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The release the draft makes has violations; change it through the draft's files.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/imports/{import}/search-settings/{type}":{"patch":{"operationId":"changeSearchSettings","summary":"Change how one type of a draft is searched","description":"Changes the type's searched fields, its typo settings or its exempt attributes, each named setting replaced whole; a setting left out stays, and `null` sets it back to its default, the import's or OrbSearch's. The Query API writes `types.json` and the draft checks the release as for `changeImport`. A draft without its own `types.json` starts from the types the live run derived. How it goes live, `showImportPublication` says: the order of the fields and the typo settings as settings within moments, the fields searched and the exempt attributes by a rebuild, since the engine reads every document again for them.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"import","in":"path","required":true,"schema":{"type":"string"}},{"name":"type","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeSearch"}}}},"responses":{"200":{"description":"The draft's search settings after the write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftSearchSettings"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The draft changed since `version` was read, and `draft` is the draft as it is now; or it was published or discarded, and `draft` is left out.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/DraftConflict"}}}},"422":{"description":"The write has violations, or the release it makes does; each names a JSON pointer and what is wrong. A change of fields to a rule they cannot express is refused here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/releases":{"get":{"operationId":"listReleases","summary":"List the releases, newest first","description":"A page of releases, newest first, each marked as the live one, the previous one a rollback goes back to, or the one going live; `links` and `meta` lead to the others.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1},"description":"The page, counted from 1. Default: 1."},{"name":"per_page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1,"maximum":100},"description":"How many to a page. Default: 15."}],"responses":{"200":{"description":"The releases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Releases"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"createRelease","summary":"Store a release","description":"Each file as its JSON object, or as text. The Query API checks the release and writes its files canonically; the same content is the same release. Storing does not publish.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewRelease"}}}},"responses":{"200":{"description":"The same release was stored before.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoredRelease"}}}},"201":{"description":"The release is stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoredRelease"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The release has violations.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not check it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/releases/{release}":{"get":{"operationId":"showRelease","summary":"Show a release with its files","description":"Its files are the canonical text the Query API wrote, so the panel and Git hold the same bytes.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"release","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The release.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoredRelease"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/releases/{release}/publish":{"post":{"operationId":"publishRelease","summary":"Publish a release, or roll back to an earlier one","description":"An index run takes it live with the current catalog, the one uploaded last that the indexer did not reject, the way `showReleasePublication` says: switched in at once, after settings, or by a rebuild. Searches read it once the run goes live, and the run's report says how it went. Publishing an earlier release again is a rollback.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"release","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"The release is published.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReleasePublished"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/releases/{release}/publication":{"get":{"operationId":"showReleasePublication","summary":"Show how publishing the release would go live, before it does","description":"What publishing the release would do with the catalog an index run would use now, decided by the decision the indexer takes: `course` says whether it goes live at once (a switch), after settings the engine applies without reindexing, or by a rebuild of the indexes, and why, as data and as a sentence. For a rebuild, `estimate_seconds` is about how long it takes, from the last rebuild; the shop keeps answering with the live release meanwhile. Publishing an earlier release again, a rollback, is decided the same way. Nothing is published.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"release","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"How publishing would go live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Publication"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"There is no catalog yet, so nothing can be indexed before one is uploaded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/index-runs":{"get":{"operationId":"listIndexRuns","summary":"List the index runs, newest first","description":"A page of index runs, newest first; `links` and `meta` lead to the others.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1},"description":"The page, counted from 1. Default: 1."},{"name":"per_page","in":"query","schema":{"type":"integer","format":"uint32","minimum":1,"maximum":100},"description":"How many to a page. Default: 15."}],"responses":{"200":{"description":"The index runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexRuns"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"rebuild","summary":"Index the current catalog with the published release again","description":"What brings search back after the engine lost its indexes.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"202":{"description":"The run is queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexRun"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"There is no catalog or no published release yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/index-runs/{index_run}":{"get":{"operationId":"showIndexRun","summary":"Show an index run with its report","description":"The report names every row of the feed the run left out, with its field and why.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"index_run","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The index run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexRun"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/live":{"get":{"operationId":"live","summary":"Show what searches read now","description":"The index run searches read, with its release and snapshot, and in its report the offer changes it applied with the time the latest arrived; 404 before any run went live.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"200":{"description":"The index run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexRun"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"There is none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/offers":{"put":{"operationId":"changeOffers","summary":"Change prices and stock without sending the catalog","description":"A batch of offer changes, each a product's price, sale, stock, availability or delivery days in one channel; the fields a change leaves out stay as they are. Each change is answered on its own: kept, or left out with a code and a sentence, such as for a product the live catalog lacks. The changes kept make one revision. Within seconds, the indexer compiles the changed products again and replaces their documents in the live indexes; batches sent close together are written together, and no index run is queued. Once searches read them, `GET /api/v1/live` and every trace name the revision. The same catalog sent again keeps the changes, another catalog supersedes them, and a rebuild never undoes a change that came after its catalog. Until the next run, what the run derived from the whole catalog stays as it was: the dictionary's counts, derived price buckets and the values each facet counts. A product an overlay hides because of its new offer leaves search at once, and a sale window that opens or closes builds the search again at that moment.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferChanges"}}}},"responses":{"202":{"description":"Each change is answered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OffersChanged"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Nothing is live yet, so no product is known; send a catalog first.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The body holds no list of offers, or more than `LIMITS.offers_per_call`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not check them.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/storage":{"get":{"operationId":"showStorage","summary":"Show what the storage keeps and the disk it takes","description":"The indexer cleans up once it starts and after every run that goes live. It keeps the snapshot of the live run, of every run still queued or running, of every draft and of an import that failed since the live run went live, the current catalog, and the newest `kept_snapshots` others that went live. Every other file goes; its row stays with `removed_at`, and so do its runs. It also drops the dictionaries of runs that are not live and the engine indexes no run uses, then measures the engine.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"200":{"description":"The storage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Storage"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"patch":{"operationId":"changeStorage","summary":"Change how many earlier catalogs are kept","description":"Takes effect at the indexer's next clean-up, after the next run that goes live or when it starts. Keeping fewer removes files; keeping more brings none back.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageChange"}}}},"responses":{"200":{"description":"The storage, with the new setting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Storage"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The setting is out of range.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/insights":{"get":{"operationId":"showInsights","summary":"Show what shoppers searched and clicked, what found nothing and the filters they chose","description":"From the search log's daily rollups, for UTC days `from` to `to`: the searches, those that found nothing, those with a click and those with results but no click, each with its denominator, per day too with the Query API's latency, where clicked hits stood, the queries searched most, those that found nothing and those that drew no click most often, the filters chosen per page, and the releases that answered. The Query API logs what it answers on `/v1/search` and on `/v1/resolve` with results, never a preview, and the clicks and views storefronts send to `/v1/events`; the control plane rolls the log up about once a minute and joins each click to its search by query ID for `LIMITS.event_hours`, so a search or a click shows within a minute or two, and late or unmatched events are counted. One kind of traffic is counted, shoppers by default; `left_out` says how many answers to tests, benchmarks and bots that left out, and how many searches each rule kept out of the queries: a query reaches them once two page views typed it that day, never with an email address or a phone number in it. Raw searches are kept 60 days; the rollups stay.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"from","in":"query","schema":{"$ref":"#/components/schemas/Day"},"description":"The first UTC day counted. Default: 29 days before `to`."},{"name":"to","in":"query","schema":{"$ref":"#/components/schemas/Day"},"description":"The last UTC day counted. Default: today."},{"name":"traffic","in":"query","schema":{"$ref":"#/components/schemas/Traffic"},"description":"The kind of traffic counted. Default: `shopper`."},{"name":"limit","in":"query","schema":{"type":"integer","format":"uint32","minimum":1,"maximum":100},"description":"Rows in each list. Default: 20."}],"responses":{"200":{"description":"The report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Insights"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A day or the traffic is not one the report can count.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/insights/campaigns":{"get":{"operationId":"showCampaigns","summary":"Show the views and clicks of each sponsored products' campaign","description":"From the daily rollups, for UTC days `from` to `to`: per campaign of the sponsored pins, the answers that served its products, the views of them and the share clicked, per day too. A view counts once per query ID and product, once a storefront reported half of the tile visible for a second or a click on it; an event counts only where an answer of its page, of its traffic, placed the product with the campaign it names, and for that answer's day, within `LIMITS.event_hours`. One kind of traffic is counted, shoppers by default. With `Accept: text/csv` the same report comes as a CSV to send to the brand, one row per campaign. The rollups outlive the 60 days of raw searches and events, so a campaign's report reaches back as far as the shop has run it.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"parameters":[{"name":"from","in":"query","schema":{"$ref":"#/components/schemas/Day"},"description":"The first UTC day counted. Default: 29 days before `to`."},{"name":"to","in":"query","schema":{"$ref":"#/components/schemas/Day"},"description":"The last UTC day counted. Default: today."},{"name":"traffic","in":"query","schema":{"$ref":"#/components/schemas/Traffic"},"description":"The kind of traffic counted. Default: `shopper`."}],"responses":{"200":{"description":"The report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignReport"}},"text/csv":{"schema":{"type":"string","description":"The report as UTF-8 CSV with a header row: `campaign`, `from`, `to`, `days`, `served`, `views`, `clicks` and `click_through`, the share clicked as a decimal such as `0.0425`, empty without views; one row per campaign, in the JSON answer's order."}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A day or the traffic is not one the report can count.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/categories":{"get":{"operationId":"listCategories","summary":"Show the category tree of what is live","description":"Every category of the live catalog with its parent, titles, paths and channels, and the type its page lists with how many entities that is, named by the release and the catalog snapshot that answered. The nodes name their parents, so the caller arranges the tree.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"responses":{"200":{"description":"The categories.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryTree"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/pages/settings":{"post":{"operationId":"showPageSettings","summary":"Show what a page shows and where each setting comes from","description":"A category page, or the search page without `category`, as a search on it reads it: its facets in display order with their groups, the order it starts in and the orders it offers, and every order a page can offer. Each setting names where it comes from: the page itself, the nearest category above it that sets it (`Sort: price, from Wohnen`), the default layout, or nothing; and whether the merchant wrote it or the index run derived it from the catalog, with the run's reason. Labels are in the locale, and `url` is the page's address. With `files`, such as a draft's `categories.json`, the settings are read from them over the live release, so a draft's page is seen before it is published; what the live index run derived stays as it derived it until then.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageSettingsRequest"}}}},"responses":{"200":{"description":"The page's settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageSettings"}}}},"400":{"description":"The body names a category the live catalog lacks, or a channel or locale the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/preview/search":{"post":{"operationId":"previewSearch","summary":"Search as a shopper would, in any context and at any time, with the trace","description":"The request as `POST /v1/search` takes it, answered against what searches read now, except that it may fix `time`: a scheduled rule or sale is checked on the day it starts. Without `time` it is answered now. The response always carries the trace, which names the rule behind every pin, boost and bury of each hit; the panel's playground shows the same answer.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"The answer, with the trace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"The request cannot be answered as sent, such as a channel or context key the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/preview/page":{"post":{"operationId":"previewPage","summary":"Open one of the shop's URLs as a shopper would, in any context and at any time","description":"A URL of the shop, such as a category page with its filters, answered as `GET /v1/resolve` answers it, with its results, in the channel, locale and context and at the time the body names. The page and its results always carry their traces; the panel's playground shows the same answer. With `files`, such as a draft's `categories.json`, the page is planned with them in place of the live release's own, so a draft's facets, sorting and URLs are seen before it is published.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagePreview"}}}},"responses":{"200":{"description":"The answer, with the trace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageResponse"}}}},"400":{"description":"The request cannot be answered as sent, such as a channel or context key the release lacks.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The draft files make a release with violations; each names its file, a JSON pointer and what is wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/preview/compare":{"post":{"operationId":"previewCompare","summary":"Say why one hit of a preview sits above another","description":"The trace a preview answered with, sent back whole, the section and two places on its page. The answer names what put the higher hit above the other, from the trace alone and in the order the engine decides: a pin, boosts and buries that outweighed the text, or the first criterion they differ in, with each hit's value. Every hit of a preview also carries `why`, its own reasons, in the rank step.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HitsToCompare"}}}},"responses":{"200":{"description":"Why one sits above the other.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HitComparison"}}}},"400":{"description":"The body is not a trace and two places.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The trace's rank step holds no hit of the section at one of the places.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/preview/find":{"post":{"operationId":"previewFind","summary":"Say why a product is not on a preview's page, or where it is","description":"The request a preview's trace holds, its time included, and the entity to find, such as `{ \"request\": { \"query\": \"ecksofa\" }, \"entity\": \"product:SOFA-MALMO\" }`. The answer walks the stages a search passes it through and names the first that drops it: not in the live index, not searched on the page, not sold in the channel, outside the channel's assortment, outside the category, a rule's filter or hide, a filter the shopper chose or the query named, the query's words, which words match in which fields and which do not, or ranked after the page, with its place and why the page's last hit sits above it. Every answer ends in what to do: open the rule, remove the filter, add a synonym, pin it. The panel's playground shows the same answer.","tags":["management"],"servers":[{"url":"http://127.0.0.1:8000","description":"The control plane"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityToFind"}}}},"responses":{"200":{"description":"Where it is, or the stage that kept it off the page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Finding"}}}},"400":{"description":"The request cannot be answered as sent, or the body names no entity.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The management key is missing or wrong.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The Query API could not be reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Nothing is searchable yet, or the engine is unreachable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"schemas":{"Health":{"type":"object","additionalProperties":false,"properties":{"status":{"type":"string","description":"`ok` while the server runs."},"version":{"type":"string","description":"The version of the binary."}},"required":["status","version"],"description":"The answer of `GET /health`."},"SearchRequest":{"type":"object","additionalProperties":false,"properties":{"query":{"type":"string","description":"What the shopper typed. Empty on a category page they only browse."},"on":{"$ref":"#/components/schemas/Surface","description":"Where the request comes from. Default: `search`."},"channel":{"description":"The channel. Default: the release's first channel.","$ref":"#/components/schemas/ChannelId"},"locale":{"description":"The locale. Default: the channel's first locale.","$ref":"#/components/schemas/Locale"},"category":{"description":"The category page being browsed.","$ref":"#/components/schemas/CategoryId"},"filters":{"$ref":"#/components/schemas/Selector","description":"The shopper's choices in the page's facets, by field: value or group codes `{ \"color\": [\"grey\", \"green\"] }`,\nbucket codes `{ \"price\": [\"100-300\"] }`, a range `{ \"width\": { \"gte\": 160, \"lte\": 220 } }`, a toggle\n`{ \"on_sale\": true }`, and the category facet's choice `{ \"category\": { \"within\": \"wohnen/sofas\" } }`. Values of\none field are alternatives; fields narrow each other."},"dismissed":{"type":"array","items":{"$ref":"#/components/schemas/Dismissal"},"description":"What the shopper took back of what the query named: those words stay text, however often the query is sent\nagain. A value, such as `{ \"field\": \"color\", \"value\": \"black\" }` or `{ \"field\": \"category\", \"value\":\n\"wohnen/sofas\" }`, or every detection of a field, such as `{ \"field\": \"width\" }` for a width a pattern read."},"redirect":{"type":"boolean","description":"Lets the response send the shopper elsewhere: to the category page a query names completely, or where a rule\nredirects. `false` answers with the results instead, as the way back from such a page does. Default: `true`."},"sort":{"description":"A sort option. Default: the page's default sort.","$ref":"#/components/schemas/SortCode"},"page":{"type":"integer","format":"uint32","minimum":1,"description":"The page, counted from 1."},"per_page":{"type":"integer","format":"uint32","minimum":1,"description":"Hits per page, up to the limit `per_page`. Default: 24, which fills rows of two, three, four and six tiles.","maximum":100},"facets":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"},"description":"Facets to count in full, besides those the page counts first (the limit `counted_facets` of its layout, and every\none with a choice): a facet the response lists as `deferred` or with `more` comes with every value when it is\nnamed here, as a shopper opening it needs. Each must be a facet of some page of the release; one the page does\nnot show is left out."},"count_only":{"type":"boolean","description":"Answers with each section's total alone: no hits, and no facets but those `facets` names, as a filter sheet's\n\"Show 128 results\" needs."},"context":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"type":"string"}},"description":"Declared context keys and their values, such as `{ \"customer_group\": \"b2b\" }`."},"time":{"description":"The time the request is answered at. The Query API stamps it on arrival; only search tests and the preview\n(`previewSearch`) may fix it, which makes scheduled rules testable.","$ref":"#/components/schemas/Timestamp"},"debug":{"type":"boolean","description":"Adds the trace to the response."},"page_view":{"description":"The page the shopper loaded, which the storefront names anew on every page load. The search log counts a query\nin the reports once two page views typed it on a day.","$ref":"#/components/schemas/PageViewId"},"traffic":{"$ref":"#/components/schemas/Traffic","description":"Whose search this is: tests and benchmarks say so, and the reports leave them out. Default: `shopper`."}},"description":"One search, category page or autocomplete request."},"Surface":{"oneOf":[{"type":"string","const":"search","description":"The search page's results."},{"type":"string","const":"category_page","description":"A category's listing."},{"type":"string","const":"autocomplete","description":"A search box's suggestions while the shopper types."}],"description":"Where a request comes from."},"ChannelId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,63}$","description":"A channel's id, such as `de` or `ch`: a market or storefront."},"Locale":{"type":"string","pattern":"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$","description":"A BCP 47 language tag such as `de`, `de-CH` or `en`. A locale falls back along its tag: `de-CH` to `de`."},"CategoryId":{"type":"string","pattern":"^\\S(.{0,254}\\S)?$","description":"The id of a category node, such as `wohnen/sofas`: the merchant's id of a `category` entity."},"Selector":{"type":"object","properties":{"category":{"description":"The entity is in this category, or below it with `within`.","$ref":"#/components/schemas/CategoryMatch"},"any":{"type":"array","items":{"$ref":"#/components/schemas/Selector"},"description":"At least one of these holds.","minItems":1},"all":{"type":"array","items":{"$ref":"#/components/schemas/Selector"},"description":"All of these hold; useful inside `any`.","minItems":1},"not":{"description":"This does not hold.","$ref":"#/components/schemas/Selector"}},"description":"Picks entities (`where`). Attribute codes and the built-in fields `brand`, `price`, `on_sale`, `availability` and\n`delivery_days` are keys of the same object; `category` takes `is` or `within`.\n\n`{ \"any\": [{ \"brand\": \"bosch\" }, { \"quality\": \"oe\" }], \"not\": { \"availability\": \"out_of_stock\" } }`","propertyNames":{"pattern":"^[a-z][a-z0-9_]{0,63}$"},"additionalProperties":{"anyOf":[{"$ref":"#/components/schemas/ValueMatch"},{"$ref":"#/components/schemas/CategoryMatch"},{"type":"array","items":{"$ref":"#/components/schemas/Selector"},"minItems":1},{"$ref":"#/components/schemas/Selector"}]}},"CategoryMatch":{"oneOf":[{"type":"object","properties":{"is":{"$ref":"#/components/schemas/OneOrMany_CategoryId"}},"required":["is"],"additionalProperties":false,"description":"Exactly this category."},{"type":"object","properties":{"within":{"$ref":"#/components/schemas/OneOrMany_CategoryId"}},"required":["within"],"additionalProperties":false,"description":"This category or any category below it."}],"description":"A category, exactly or including everything below it."},"OneOrMany_CategoryId":{"description":"One value, or a list meaning any of them.","anyOf":[{"$ref":"#/components/schemas/CategoryId"},{"type":"array","items":{"$ref":"#/components/schemas/CategoryId"},"minItems":1}]},"ValueMatch":{"description":"A plain value (equal), a list (any of them), or an operator object such as { \"lt\": 20 }.","anyOf":[{"$ref":"#/components/schemas/Scalar"},{"type":"array","items":{"$ref":"#/components/schemas/Scalar"},"minItems":1},{"$ref":"#/components/schemas/Comparison"}]},"Scalar":{"anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string"}],"description":"A single value in a selector, filter or test: text, a number or a boolean. Dates are text, such as `2026-10-01`."},"Comparison":{"type":"object","additionalProperties":false,"properties":{"lt":{"$ref":"#/components/schemas/Scalar"},"lte":{"$ref":"#/components/schemas/Scalar"},"gt":{"$ref":"#/components/schemas/Scalar"},"gte":{"$ref":"#/components/schemas/Scalar"},"between":{"type":"array","items":{"$ref":"#/components/schemas/Scalar"},"minItems":2,"maxItems":2,"description":"From the first value to the second, both included."},"exists":{"type":"boolean","description":"True: the field has a value. False: it has none."}},"description":"Compares a field's value. Several operators in one object combine with AND: `{ \"gte\": 10, \"lt\": 20 }`."},"Dismissal":{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldCode","description":"The field the detection filtered, or `category`."},"value":{"description":"The value, a value or group code or a category id; without one, every detection of the field.","$ref":"#/components/schemas/Scalar"}},"required":["field"],"description":"A detection the shopper removed."},"FieldCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"A field a selector, filter or facet reads: an attribute code, or one of the built-in fields `category`, `brand`, `price`, `on_sale`, `availability`, `delivery_days` and `rating`."},"SortCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"The code of a sort option, such as `price_asc`. `relevance` is built in."},"Timestamp":{"type":"string","format":"date-time","description":"A point in time as RFC 3339 with an offset, such as `2026-11-27T00:00:00+01:00`."},"PageViewId":{"type":"string","pattern":"^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$","description":"One page a shopper loaded, a UUID the storefront makes in memory when the page loads and sends with each of its searches. It is never stored on the shopper's device; the search log counts distinct page views by it."},"Traffic":{"oneOf":[{"type":"string","const":"shopper","description":"Everyone a storefront serves that none of the below is."},{"type":"string","const":"test","description":"End-to-end tests, which mark their requests so."},{"type":"string","const":"bench","description":"A latency benchmark, which marks its requests so."},{"type":"string","const":"bot","description":"A crawler or another bot, as uap-core's maintained user agent rules name it; the Query API decides this from the\n`User-Agent` header of a request that says it is a shopper's."}],"description":"Whose searches these are. A report counts one kind and says how many answers to the others it left out."},"SearchResponse":{"type":"object","additionalProperties":false,"properties":{"query_id":{"$ref":"#/components/schemas/QueryId","description":"Names this answer for clicks and purchases."},"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that answered."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog that was searched."},"query":{"$ref":"#/components/schemas/QueryText"},"redirect":{"description":"Where to send the shopper when they submit the query: the category or brand page it names completely, or where\na rule redirects. A client that does not follow it, such as a search box suggesting as the shopper types, shows the\nsections, which hold what that page shows; a rule's redirect to a fixed URL has none.","$ref":"#/components/schemas/RedirectTarget"},"resolution":{"description":"What the query named.","$ref":"#/components/schemas/Resolution"},"relaxed":{"type":"boolean","description":"Nothing matched the resolved search, so it was repeated without what the query named; the resolution says what\nthat was, and `filters` no longer holds it."},"filters":{"type":"array","items":{"$ref":"#/components/schemas/AppliedFilter"},"description":"Every filter that narrowed the results the page lists, each removable by the shopper."},"listed":{"$ref":"#/components/schemas/TypeCode","description":"The type the page lists: its section pages through the results and carries the facets, and `filters` narrow\nit. Products, unless the category page, or the category the query named, holds another type with offers, such\nas the services of a workshop's category."},"sorts":{"type":"array","items":{"$ref":"#/components/schemas/SortOption"},"description":"The orders the page offers, in display order, one of them marked as the one the page starts in. A storefront\ndraws its sort control from them and puts `sort` in a URL only where the shopper chose another order than the\nstart. None where the release lays out no options: the page offers no choice."},"sections":{"type":"array","items":{"$ref":"#/components/schemas/Section"},"description":"The results, one section per entity type, in order."},"placements":{"type":"array","items":{"$ref":"#/components/schemas/PlacedBanner"},"description":"The banners and teasers rules place on this page, in the order it draws them: the top ones, those among the\ntiles by position, the bottom ones. None on the autocomplete surface and for a request for totals alone."},"suggestions":{"type":"array","items":{"$ref":"#/components/schemas/Suggestion"},"description":"On the autocomplete surface, the queries the shopper may mean, in the order a search box lists them: the text\nsuggestions, then the scoped ones."},"trace":{"description":"Why the response is what it is; only when the request asked for it.","$ref":"#/components/schemas/Trace"}},"required":["query_id","release","snapshot","query","listed"]},"QueryId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$","description":"The id of one answered query, a UUIDv7 whose first 48 bits are the millisecond it was answered; clicks, views and purchases name it."},"ReleaseId":{"type":"string","pattern":"^\\S{1,128}$","description":"The id of a release. A release is named by its content and never changes."},"SnapshotId":{"type":"string","pattern":"^\\S{1,128}$","description":"The id of a catalog snapshot. A snapshot is named by its content and never changes."},"QueryText":{"type":"object","additionalProperties":false,"properties":{"typed":{"type":"string"},"searched":{"type":"string"}},"required":["typed","searched"],"description":"The query as typed, and the text that was searched after rules removed words and resolution took what it named."},"RedirectTarget":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string"},"entity":{"description":"The entity whose URL it is, when the redirect named one: the category or brand a query landed on, or a rule's\ntarget.","$ref":"#/components/schemas/EntityRef"},"rule":{"description":"The rule that redirects; none when the query lands on the page it names.","$ref":"#/components/schemas/RuleId"}},"required":["url"]},"EntityRef":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}:\\S(.{0,254}\\S)?$","description":"An entity named with its type, `type:id`, such as `product:SOFA-LUND-3`."},"RuleId":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$","description":"A rule's id, unique per release, such as `home/out-of-stock-last`. It appears in traces, tests, diffs and filter chips, so it is visible to storefronts and holds no secrets."},"Resolution":{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"filters":{"$ref":"#/components/schemas/Selector","description":"The detected filters, such as `{ \"color\": \"grey\", \"width\": { \"lte\": 220 } }`."},"text":{"type":"string"},"complete":{"type":"boolean","description":"Nothing is left as text."},"left_out":{"type":"array","items":{"$ref":"#/components/schemas/AppliedFilter"},"description":"What a relaxed search left out of what the query named, each as the filter it would have been, with its labels,\nso a storefront can say what the results leave out. Empty unless the response is `relaxed`."}},"required":["text","complete"],"description":"The categories, brands and values a query named, and the text that is left."},"AppliedFilter":{"type":"object","additionalProperties":false,"properties":{"where":{"$ref":"#/components/schemas/Selector","description":"What the filter keeps, with exactly one field or `category`, such as `{ \"category\": { \"within\": \"wohnen\" } }`."},"source":{"$ref":"#/components/schemas/FilterSource"},"rule":{"description":"The rule that added it, when `source` is `rule`.","$ref":"#/components/schemas/RuleId"},"label":{"type":"string","description":"The field's name in the request's locale, as its facet is labeled, such as \"Farbe\" or \"Kategorie\"; none for a\nrule's filter of several fields."},"labels":{"type":"object","additionalProperties":{"type":"string"},"description":"What the values and categories it names read as in the request's locale, by code or id: an option's or group's\nlabel, a brand's or a category's title, such as `{ \"grey\": \"Grau\" }` or `{ \"textilien/teppiche\": \"Teppiche\" }`.\nA number, a range and a value nothing labels have none."}},"required":["where","source"],"description":"A filter as the storefront shows it: a chip the shopper can remove. Removing it means taking `where` out of the\nrequest's filters, or for a filter the query named, sending its field and value in `dismissed`, which keeps its\nwords as text."},"FilterSource":{"oneOf":[{"type":"string","const":"shopper","description":"The shopper chose it."},{"type":"string","const":"query","description":"The query named it."},{"type":"string","const":"rule","description":"A rule added it."}]},"TypeCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"The code of an entity type, such as `product`, `category` or a merchant's own `store`."},"SortOption":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/SortCode","description":"What a request's `sort` names to choose it; `relevance` is built in."},"label":{"type":"string","description":"In the request's locale."},"default":{"type":"boolean","description":"The page starts in this order while the shopper chooses none: the sort its category or the layout's default\nsets, or relevance where the request has words, which rank by it."}},"required":["code","label"],"description":"An order a page offers."},"Section":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"total":{"type":"integer","format":"uint64","minimum":0,"description":"How many entities match in all, counted exactly however many they are."},"reachable":{"type":"integer","format":"uint64","minimum":0,"description":"How many of them pages reach: for the listed section the total, or the limit `reachable_hits` when more match;\nfor a section beside it, which does not page, the hits it shows. A storefront links and loads no page past them\nand says why when the total is larger."},"upper_bound":{"type":"boolean","description":"The total is an upper bound: a free range over a value that differs between a tile's variants narrows it beside\nanother variant-level choice, and one variant may meet the range while another meets the choice. The trace says\nwhich range."},"hits":{"type":"array","items":{"$ref":"#/components/schemas/Hit"},"description":"This page's tiles; none when the request asked for the totals alone."},"facets":{"type":"array","items":{"$ref":"#/components/schemas/FacetResult"},"description":"The page's facets in display order, each counted under every filter but its own, so choosing a value never\nmakes its siblings vanish. A facet without values to show is left out."}},"required":["type","total","reachable","hits"],"description":"The results of one entity type."},"Hit":{"type":"object","additionalProperties":false,"properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"variant":{"description":"The variant the tile shows, when the listing grain is finer than the product or the shopper's choices on\nvariant-level values pick one, such as the grey variant of a sofa under a grey filter.","$ref":"#/components/schemas/EntityId"},"fields":{"type":"object","additionalProperties":true,"description":"The type's result fields, in the request's locale and channel."},"sponsored":{"description":"A rule's paid pin placed it here: a storefront labels the tile as an ad and names the campaign in the tile's\nclicks and views.","$ref":"#/components/schemas/Sponsored"}},"required":["entity","fields"],"description":"One result tile."},"EntityId":{"type":"string","pattern":"^\\S(.{0,254}\\S)?$","description":"The merchant's id of an entity, unique per type, such as `SOFA-LUND-3`. It travels unchanged; OrbSearch never rewrites it."},"Sponsored":{"type":"object","additionalProperties":false,"properties":{"campaign":{"$ref":"#/components/schemas/Campaign","description":"The merchant's name for it, which the campaign report counts by."}},"required":["campaign"],"description":"The campaign a paid placement belongs to: `{ \"campaign\": \"Michelin spring\" }`."},"Campaign":{"type":"string","pattern":"^\\S(?:.{0,198}\\S)?$","description":"A sponsored placement's campaign, the merchant's name for it, such as `Michelin spring`: one line of up to 200 characters, which reports and their CSV group views and clicks by."},"FacetResult":{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldCode"},"kind":{"$ref":"#/components/schemas/FacetKind"},"label":{"type":"string"},"group":{"description":"The group the page draws it in, such as a tyre's width under \"Tyre size\". The facets of a group follow one\nanother in the group's order, so a storefront without a part for the group draws them one under the other.","$ref":"#/components/schemas/GroupResult"},"values":{"type":"array","items":{"$ref":"#/components/schemas/FacetValue"},"description":"The values that have results, and every chosen value even without, so it can always be unchosen. A list or\nswatch carries the most frequent ones up to its cap, and `more` says how many it leaves out. A range has none;\nits `min` and `max` bound it. A `deferred` facet has none yet."},"more":{"type":"integer","format":"uint64","minimum":0,"description":"How many more values with results the facet has than `values` holds: the cap left them out, which is the limit\n`facet_values` or the layout's `limit`. Naming the facet in a request's `facets` returns them all."},"deferred":{"type":"boolean","description":"The page did not count this facet (the limit `counted_facets`), so it has no `values` yet and may have none to\nshow. Naming it in a request's `facets` counts it."},"upper_bound":{"type":"boolean","description":"The counts are upper bounds. That happens only under multi-select across two variant axes inside one tile, and\nunder a free range over a variant-level value that differs inside a tile; the trace says which."},"single":{"type":"boolean","description":"One value at most: a radio group with \"Any\" first."},"description":{"type":"string","description":"Text under the facet's name, read with its group."},"headings":{"type":"array","items":{"$ref":"#/components/schemas/FacetHeading"},"description":"Parts of the list under headings of their own, in order; values no heading names follow the last one."},"search":{"type":"boolean","description":"A search box belongs above the values."},"display":{"$ref":"#/components/schemas/FacetDisplay"},"custom_display":{"description":"The shop's own storefront display, as the release names it; a storefront without it draws `display`.","$ref":"#/components/schemas/DisplayCode"},"min":{"type":"number","description":"A range: the lowest value among the results without the range's own choice, such as the cheapest price."},"max":{"type":"number","description":"A range: the highest value among the results without the range's own choice."},"histogram":{"type":"array","items":{"$ref":"#/components/schemas/Bin"},"description":"A histogram: the bins in ascending order from the one the cheapest tile falls in to the one the dearest does,\nempty ones included so the bars keep their places, each with the tiles counted in it under every filter but\nthe facet's own. The bins are a fixed ladder of preferred numbers, 24 to the decade and each 8 to 15 percent above\nthe one before, the same under every filter, so a bar says where products sit, not exactly how many: a tile with\nseveral prices (one per variant) counts in every bin one of them falls in, and the chosen range may cut a bin\nin two. The range's `min` and `max` still bound the slider. None where the prices fall in a single bin, and\nwhile the facet is `deferred`; naming it in a request's `facets` counts it, with the same bins."},"unit":{"description":"The unit of a quantity's values and bounds, such as `cm`.","$ref":"#/components/schemas/Unit"},"currency":{"description":"The currency of a price's values and bounds, the channel's.","$ref":"#/components/schemas/Currency"}},"required":["field","kind","label","values"],"description":"A facet with its values and counts."},"FacetKind":{"oneOf":[{"type":"string","enum":["list","swatch","hierarchical","range","buckets","toggle"]},{"type":"string","const":"relative","description":"Dates relative to the request's time, such as \"new in the last 30 days\". Bucket bounds are days."}]},"GroupResult":{"type":"object","additionalProperties":false,"properties":{"group":{"$ref":"#/components/schemas/GroupCode"},"label":{"type":"string","description":"The heading, in the request's locale."},"display":{"$ref":"#/components/schemas/GroupDisplay"}},"required":["group","label"],"description":"The group a facet stands in, as every facet of it says."},"GroupCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"The code of a group of facets on a page, such as `tyre_size`."},"GroupDisplay":{"oneOf":[{"type":"string","const":"finder","description":"One select beside the next, each with \"Any\" first and every value counted under the choices of the others, such\nas a tyre's width, ratio and rim. Each of its facets is shown as `select`."}],"description":"How a group of facets is drawn."},"FacetValue":{"type":"object","additionalProperties":false,"properties":{"value":{"$ref":"#/components/schemas/Scalar","description":"What a request's `filters` name to choose it: a value or group code, a number, `true`, a category id, or a\nbucket's code written as its bounds, such as `100-300`, `-4` or `4-`."},"label":{"type":"string"},"count":{"type":"integer","format":"uint64","minimum":0},"bounds":{"description":"A bucket's bounds, such as `{ \"gte\": 100, \"lt\": 300 }`; selecting the bucket filters the field by them.","$ref":"#/components/schemas/Comparison"},"parent":{"description":"In a hierarchical facet, the value one level up.","$ref":"#/components/schemas/Scalar"},"selected":{"type":"boolean"},"swatch":{"type":"string"},"image":{"type":"string","description":"A swatch image or pattern, or a brand's logo where the facet shows images; its URL."},"description":{"type":"string","description":"What the value means, read with it."}},"required":["value","label","count"]},"FacetHeading":{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string"},"values":{"type":"array","items":{"$ref":"#/components/schemas/Scalar"}}},"required":["label","values"],"description":"A heading inside a facet and the values it stands above."},"FacetDisplay":{"oneOf":[{"type":"string","const":"slider","description":"A range: a two-thumb slider above the two inputs, which stay."},{"type":"string","const":"histogram","description":"A range over the price: a slider with the distribution of the prices drawn behind it, in the bins of a fixed\nladder of preferred numbers (`histogram` of the response). Prices that fall in a single bin get the plain slider."},{"type":"string","const":"stars","description":"A rating: stars beside each bucket; its words carry the meaning."},{"type":"string","const":"grades","description":"An option's values on badges in their own swatch colors, such as the energy label's A to G, in the order the\nvalues are listed. Every listed value needs a swatch."},{"type":"string","const":"buttons","description":"Values or buckets as a wrap of toggle buttons, such as sizes."},{"type":"string","const":"select","description":"One value at most, chosen from one native select with \"Any\" first, for a long ordered list such as a tyre\nwidth."},{"type":"string","const":"images","description":"Each value's image beside its name: a brand's logo, or the image an option's value carries, such as an icon."}],"description":"How a facet's values look beyond what its kind says."},"DisplayCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"The name of a storefront display of the shop's own, such as `color_tiles`, as the shop registered it."},"Bin":{"type":"object","additionalProperties":false,"properties":{"gte":{"type":"number"},"lt":{"type":"number"},"count":{"type":"integer","format":"uint64","minimum":0}},"required":["gte","lt","count"],"description":"One bar of a histogram: the tiles with a price from `gte` up to below `lt`."},"Unit":{"type":"string","enum":["mm","cm","m","in","ft","g","kg","lb","oz","ml","l","w","kw","wh","kwh","mah","v","lm","kelvin","celsius","hz","mb","gb","tb","day","month","year"],"description":"A unit OrbSearch converts between. The registry is built in: conversion factors are physics, not configuration."},"Currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"An ISO 4217 currency code, such as `EUR`."},"PlacedBanner":{"type":"object","additionalProperties":false,"properties":{"slot":{"$ref":"#/components/schemas/Slot"},"position":{"type":"integer","format":"uint32","minimum":0,"description":"With `grid`: the tile of the listed section it sits before, counted from 1 across the pages, so on page `p` it\nstands before hit `position - (p - 1) * per_page`. A higher rule's banner at the position asked for moves it to\nthe next free one."},"kind":{"$ref":"#/components/schemas/BannerKind"},"title":{"type":"string"},"text":{"type":"string"},"image":{"$ref":"#/components/schemas/PlacedImage"},"link":{"description":"Where it leads; left out for a banner that only informs, and for one whose page the catalog no longer holds,\nwhich the trace names.","$ref":"#/components/schemas/Link"},"rule":{"$ref":"#/components/schemas/RuleId","description":"The rule that placed it."}},"required":["slot","kind","title","rule"],"description":"A banner a rule placed on this page, in the request's locale. It is not a hit: the sections' totals, facets and\nhits are the same without it."},"Slot":{"oneOf":[{"type":"string","const":"top","description":"Above the results, on the first page. Banners of several rules stand in rule order, the highest first."},{"type":"string","const":"grid","description":"Among the tiles, before the one at its position, on the page that holds that tile."},{"type":"string","const":"bottom","description":"After the results, on the last page. Banners of several rules stand in rule order, the highest first."}],"description":"Where on a page a banner sits."},"BannerKind":{"oneOf":[{"type":"string","const":"banner","description":"A campaign's banner, as wide as the results."},{"type":"string","const":"teaser","description":"A card to a page, such as a buying guide, the size of a tile."}]},"PlacedImage":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string"},"alt":{"type":"string","description":"What the image shows, in the request's locale."}},"required":["url","alt"]},"Link":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string"},"entity":{"description":"The entity whose page it is; none for a fixed URL.","$ref":"#/components/schemas/EntityRef"}},"required":["url"],"description":"A URL a response leads to, written by the merchant's routes where it is an entity's page."},"Suggestion":{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string","description":"The query it suggests, as the catalog writes it."},"scope":{"description":"The category it searches in; none for a text suggestion.","$ref":"#/components/schemas/SuggestionScope"}},"required":["text"],"description":"A query the shopper may mean while typing. A text suggestion completes what was typed with the catalog's own words,\nsuch as \"Ecksofa\" for \"ecks\"; a scoped one searches such a completion within a category, \"Ecksofa in Sofas\"."},"SuggestionScope":{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"title":{"type":"string"}},"required":["category","title"],"description":"The category a scoped suggestion searches in, and its title in the request's locale."},"Trace":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId"},"snapshot":{"$ref":"#/components/schemas/SnapshotId"},"publishing":{"description":"A release was being published while this request was answered; either release's live settings, such as its\nsynonyms or typo settings, may have applied.","$ref":"#/components/schemas/ReleaseId"},"live":{"description":"The index run this was answered from and how it took the release live: switched in, with settings, or built.","$ref":"#/components/schemas/WentLive"},"derived":{"type":"array","items":{"type":"string"},"description":"The release's files the live run derived from its catalog because the release leaves them out, such as\n`categories.json` with the facets or `ranking.json` with the sort options. They answered as if written; the run\nreport gives the reason for each of their settings."},"request":{"$ref":"#/components/schemas/SearchRequest","description":"The request as it was answered: with the time it arrived at, and the channel and locale it was answered in.\nReplayed as a search test or in the preview, against the same release and snapshot, it gives the same response."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/Step"},"description":"One entry per step, in the order the steps ran."},"timings":{"description":"How long the Query API took for the request. A page's listing leaves it to the page's trace, which measures the\nwhole page.","$ref":"#/components/schemas/Timings"}},"required":["release","snapshot","request","steps"]},"WentLive":{"type":"object","additionalProperties":false,"properties":{"run":{"type":"integer","format":"uint64","minimum":0},"course":{"$ref":"#/components/schemas/Course"},"offers":{"type":"integer","format":"uint64","minimum":0,"description":"The offers revision: the latest batch of offer changes the run's indexes hold, applied on top of its snapshot by\nthe run or written into its indexes since; left out when they hold none."}},"required":["run","course"],"description":"The index run a response was answered from, how it took its release live, and the offer changes its documents\nhold. With the release and the snapshot, they name what answered: a replay with the same three gives the same answer."},"Course":{"type":"object","additionalProperties":false,"properties":{"way":{"$ref":"#/components/schemas/Way"},"because":{"$ref":"#/components/schemas/Cause"},"sentence":{"type":"string","description":"The same in a sentence, such as \"Goes live at once: only rules.json and categories.json changed what the Query\nAPI reads.\""}},"required":["way","because","sentence"],"description":"The way a release goes live and why, as data and as a sentence."},"Way":{"oneOf":[{"type":"string","const":"switch","description":"The live indexes and the catalog dictionary stay, and the Query API loads the new release. It goes live at once."},{"type":"string","const":"settings","description":"Settings the engine applies without reindexing, such as synonyms, are written to the live indexes, then the\nrelease is switched in. It goes live within moments."},{"type":"string","const":"rebuild","description":"New indexes are built from the catalog beside the live ones and swapped in. The shop keeps answering with the\nlive release meanwhile, and the time grows with the catalog."}],"description":"How a release goes live, from the cheapest to the dearest."},"Cause":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","const":"nothing_live"}},"required":["id"],"description":"Rebuild: no index run is live yet."},{"type":"object","properties":{"id":{"type":"string","const":"new_catalog"}},"required":["id"],"description":"Rebuild: the catalog is not the live one's, so its documents are new."},{"type":"object","properties":{"id":{"type":"string","const":"asked"}},"required":["id"],"description":"Rebuild: the merchant asked for one, such as after the engine lost its data."},{"type":"object","properties":{"since":{"$ref":"#/components/schemas/Timestamp"},"id":{"type":"string","const":"out_of_date"}},"required":["id","since"],"description":"Rebuild: a sale window opened or closed, or an overlay ended, at `since`, after the live run built its documents."},{"type":"object","properties":{"indexes":{"type":"array","items":{"type":"string"}},"id":{"type":"string","const":"engine_lacks_indexes"}},"required":["id","indexes"],"description":"Rebuild: the engine no longer holds these live indexes."},{"type":"object","properties":{"was":{"type":"string"},"now":{"type":"string"},"id":{"type":"string","const":"indexer_changed"}},"required":["id","now"],"description":"Rebuild: the live run was built by another version of the indexer, or by one that recorded nothing to compare\nwith. `was` is left out for the latter."},{"type":"object","properties":{"files":{"type":"array","items":{"type":"string"}},"id":{"type":"string","const":"documents_changed"}},"required":["id","files"],"description":"Rebuild: these files change what the engine's documents are built from or the shape of its indexes: types,\nchannels, attributes, overlays, the feed mapping, the business score, the sort options or the facets pages\ncount."},{"type":"object","properties":{"files":{"type":"array","items":{"type":"string"}},"id":{"type":"string","const":"settings_changed"}},"required":["id","files"],"description":"Settings: these files change only settings the engine applies without reindexing."},{"type":"object","properties":{"files":{"type":"array","items":{"type":"string"}},"id":{"type":"string","const":"serving_changed"}},"required":["id","files"],"description":"Switch: these files change only what the Query API reads: rules, the layout of pages, routes and redirects."},{"type":"object","properties":{"id":{"type":"string","const":"unchanged"}},"required":["id"],"description":"Switch: the live run already has this release and this catalog, so nothing changes."}],"description":"What decided the way. A file is named as the release writes it, such as `rules.json`, and a derived file by the\nname it would have."},"Step":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"query":{"type":"string"},"normalized":{"type":"string"},"step":{"type":"string","const":"normalize"}},"required":["step","query","normalized"],"description":"The query as the engine's tokenizer reads it."},{"type":"object","additionalProperties":false,"properties":{"resolution":{"$ref":"#/components/schemas/Resolution"},"detections":{"type":"array","items":{"$ref":"#/components/schemas/Detection"},"description":"Each word or phrase that became the category or a filter, with the entry it matched."},"kept":{"type":"array","items":{"$ref":"#/components/schemas/KeptText"},"description":"Words that matched something and stay text all the same, each with the reason."},"yielded":{"type":"array","items":{"$ref":"#/components/schemas/Detection"},"description":"What the words named in a field the shopper chose themselves: their choice replaces it, so the words neither\nfilter nor are searched, as \"graues\" after the shopper ticked blue on \"graues Sofa\"."},"named":{"type":"array","items":{"$ref":"#/components/schemas/AppliedFilter"},"description":"What the query named as its chips read in the request's locale, held to or left out: the category and one\nfilter per field, each with its labels, such as the category's title."},"step":{"type":"string","const":"resolve"}},"required":["step","resolution"],"description":"What the query named, and the text that is left."},{"type":"object","additionalProperties":false,"properties":{"phase":{"$ref":"#/components/schemas/RulePhase"},"rules":{"type":"array","items":{"$ref":"#/components/schemas/RuleTrace"}},"step":{"type":"string","const":"rules"}},"required":["step","phase","rules"],"description":"Every candidate rule of one phase, in rule order, with the one state it ended in."},{"type":"object","additionalProperties":false,"properties":{"sort":{"$ref":"#/components/schemas/SortCode","description":"The sort option; `relevance` is built in."},"source":{"$ref":"#/components/schemas/SortSource"},"category":{"description":"The category whose node sets the sort, when `source` is `category`.","$ref":"#/components/schemas/CategoryId"},"reason":{"type":"string","description":"Why, as a sentence, such as \"The category wohnen/sofas sets price_desc as its default sort.\""},"step":{"type":"string","const":"sort"}},"required":["step","sort","source","reason"],"description":"The order the page lists its hits in, and where that choice came from."},{"type":"object","additionalProperties":false,"properties":{"listed":{"$ref":"#/components/schemas/Listed"},"searches":{"type":"array","items":{"$ref":"#/components/schemas/PlannedSearch"}},"step":{"type":"string","const":"plan"}},"required":["step","listed","searches"],"description":"The searches the engine is asked for, one per section, and the type the page lists."},{"type":"object","additionalProperties":false,"properties":{"round_trip_ms":{"type":"number","format":"double","description":"From sending the call to reading its answer, in milliseconds."},"searches":{"type":"array","items":{"$ref":"#/components/schemas/ExecutedSearch"}},"step":{"type":"string","const":"execute"}},"required":["step","round_trip_ms","searches"],"description":"The engine's answer, in one multi-search call."},{"type":"object","additionalProperties":false,"properties":{"dropped":{"type":"array","items":{"$ref":"#/components/schemas/Selector"},"description":"What the next search leaves out: the category and the filters the query named, one field each."},"text":{"type":"string","description":"The text it searches."},"step":{"type":"string","const":"relax"}},"required":["step","dropped","text"],"description":"Nothing matched the resolved search, so it was searched again with less of what the query named."},{"type":"object","additionalProperties":false,"properties":{"facets":{"type":"array","items":{"$ref":"#/components/schemas/CountedFacet"}},"deferred":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"},"description":"The facets the page lists without counting them: those past the limit `counted_facets` that no choice and\nno request named, and the facets of a group that none of those reached."},"groups":{"type":"array","items":{"$ref":"#/components/schemas/CountedGroup"},"description":"The page's groups of facets and how each was counted."},"step":{"type":"string","const":"facets"}},"required":["step","facets"],"description":"How each facet was counted, and where a count can only be an upper bound."},{"type":"object","additionalProperties":false,"properties":{"sections":{"type":"array","items":{"$ref":"#/components/schemas/RankedSection"}},"step":{"type":"string","const":"rank"}},"required":["step","sections"],"description":"Why each hit of the page sits where it does: the pin that placed it, the boosts and buries that weighed it, and\nits business score."},{"type":"object","additionalProperties":false,"properties":{"sections":{"type":"array","items":{"$ref":"#/components/schemas/AssembledSection"}},"step":{"type":"string","const":"assemble"}},"required":["step","sections"],"description":"The sections of the response, in order."},{"type":"object","additionalProperties":false,"properties":{"suggestions":{"type":"array","items":{"$ref":"#/components/schemas/SuggestedText"}},"step":{"type":"string","const":"suggest"}},"required":["step","suggestions"],"description":"The suggestions of the autocomplete surface, in order, each with the entry of the catalog dictionary it came from."}],"description":"One step of answering a request, and what it decided."},"Detection":{"type":"object","additionalProperties":false,"properties":{"words":{"type":"string","description":"The words as the query wrote them, such as `graues`."},"as":{"$ref":"#/components/schemas/Selector","description":"What they became, with exactly one field or `category`, as the filter chip writes it."},"entry":{"type":"string","description":"Where the meaning comes from, as a sentence, such as \"the alias \\\"graues\\\" of the color group Grau\"."},"rule":{"description":"The rule that decided what the words mean, when one did.","$ref":"#/components/schemas/RuleId"},"pattern":{"type":"string","description":"The pattern that read them, when one did: the id of the release's pattern, or the built-in pattern's name."},"synonym":{"description":"The entry of `synonyms.json` that let the words name it, such as `de/groups/0`: \"couch\" names the category Sofas\nthrough the group of \"couch\" and \"sofa\".","$ref":"#/components/schemas/SynonymId"}},"required":["words","as","entry"],"description":"Words of the query that became the category or a filter."},"SynonymId":{"type":"string","pattern":"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*/(groups|one_way|never)/(0|[1-9][0-9]{0,5})$","description":"A synonym entry's place in `synonyms.json`: its language, its kind and its index there, such as `de/groups/0`, `de/one_way/2` or `de/never/0`. A delete moves the entries after it up, so a write names the draft version it read."},"KeptText":{"type":"object","additionalProperties":false,"properties":{"words":{"type":"string"},"reason":{"type":"string","description":"Why they stay text, such as \"they name the category Sofas and the brand Sofa Company alike\"."},"candidates":{"type":"array","items":{"$ref":"#/components/schemas/Selector"},"description":"What they could have meant."}},"required":["words","reason"],"description":"Words that matched something and stay text."},"RulePhase":{"oneOf":[{"type":"string","const":"understand","description":"Rules that resolve terms or remove words, before the query is resolved."},{"type":"string","const":"redirect","description":"Rules that redirect; the highest one ends planning."},{"type":"string","const":"shape","description":"Rules that filter, hide, pin, boost, bury, order sections and place banners."}]},"RuleTrace":{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string","description":"The rule's line from `rules.json`, so the trace reads without the release."},"origin":{"$ref":"#/components/schemas/Origin"},"state":{"$ref":"#/components/schemas/RuleState"},"matched":{"type":"string","description":"The part of the condition that matched, as a sentence."},"failed":{"type":"string","description":"The part of the condition that failed, as a sentence."},"reason":{"type":"string","description":"Why the rule was inactive or not applied."},"effects":{"type":"array","items":{"$ref":"#/components/schemas/EffectTrace"}}},"required":["rule","description","state"],"description":"What became of one candidate rule."},"Origin":{"type":"object","additionalProperties":false,"properties":{"import":{"type":"string","description":"What it was imported from, such as the previous search's export."},"ref":{"type":"string"}},"required":["import","ref"],"description":"Where an imported rule or overlay came from."},"RuleState":{"oneOf":[{"type":"string","const":"applied","description":"Every effect took place."},{"type":"string","const":"partly_applied","description":"Some effects were moved or skipped; each says why."},{"type":"string","const":"not_applied","description":"The rule matched, but a higher rule's redirect ended planning."},{"type":"string","const":"not_matched","description":"The condition failed."},{"type":"string","const":"inactive","description":"Disabled, or outside its schedule."}]},"EffectTrace":{"type":"object","additionalProperties":false,"properties":{"effect":{"$ref":"#/components/schemas/EffectKind"},"outcome":{"$ref":"#/components/schemas/EffectOutcome"},"section":{"description":"The section it acted in, by type: an effect that reaches the sections of several types is traced once for each.","$ref":"#/components/schemas/TypeCode"},"target":{"type":"string","description":"What the effect was about, as a sentence, such as \"brand is bosch\", \"product:SOFA-LUND-3\" or, for a placement,\nthe banner's title."},"slot":{"description":"Placements: where on the page the banner sits.","$ref":"#/components/schemas/Slot"},"position":{"type":"integer","format":"uint32","minimum":0,"description":"Pins and grid placements: the position asked for."},"placed":{"type":"integer","format":"uint32","minimum":0,"description":"Pins: the slot the plan expects it to take. A higher pin that fails a filter frees its slot only in the engine,\nso where a hit landed is the `rank` step's. Grid placements: the position it took."},"strength":{"$ref":"#/components/schemas/Strength"},"reason":{"type":"string","description":"Why the effect was moved or skipped, such as \"skipped: limit\", or for a placement this page does not show, why\nnot, such as \"position 30 is on page 2\"."},"engine":{"description":"How the engine carried it out; for developers.","$ref":"#/components/schemas/EngineTrace"}},"required":["effect","outcome"],"description":"What became of one effect."},"EffectKind":{"type":"string","enum":["resolve","rewrite","redirect","filter","hide","pin","boost","bury","sections","place"]},"EffectOutcome":{"type":"string","enum":["applied","moved","skipped"]},"Strength":{"type":"string","enum":["slight","medium","strong"]},"EngineTrace":{"type":"object","additionalProperties":false,"properties":{"rule":{"type":"string"},"weight":{"type":"number"}},"required":["rule"],"description":"The engine rule that executed a pin, boost or bury, and the weight it gave."},"SortSource":{"oneOf":[{"type":"string","const":"shopper","description":"The shopper chose it; it wins over any default."},{"type":"string","const":"text","description":"The request has words, which rank by relevance unless the shopper chose another order."},{"type":"string","const":"category","description":"The nearest category node that sets a default sort, up the tree from the page's category."},{"type":"string","const":"default","description":"The `default` layout of `categories.json`."},{"type":"string","const":"built_in","description":"Nothing sets a default sort: relevance, which is built in."}],"description":"Where the order of a page came from."},"Listed":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"reason":{"type":"string","description":"Why, as a sentence, such as \"The category werkstatt/bremsenservice holds 7 service and no product entities.\""}},"required":["type","reason"],"description":"The type a page lists, whose section pages through the results, carries the facets and takes what the query named."},"PlannedSearch":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode","description":"The section it fills."},"index":{"type":"string","description":"The engine index it searches."},"text":{"type":"string","description":"The text it searches for."},"filter":{"type":"string","description":"The engine filter, when the search is narrowed."},"sort":{"type":"array","items":{"type":"string"},"description":"The engine sort, when the request chose one."},"page":{"type":"integer","format":"uint32","minimum":0},"per_page":{"type":"integer","format":"uint32","minimum":0,"description":"Hits it returns; none for a search that only counts."},"facets":{"type":"array","items":{"type":"string"},"description":"The engine fields whose values it counts."},"without":{"description":"The facet it counts under every filter but that facet's own, so that choosing a value keeps its siblings; its\nhits are not shown.","$ref":"#/components/schemas/FieldCode"}},"required":["type","index","text","page","per_page"],"description":"One search of the plan, as the engine receives it; for developers."},"ExecutedSearch":{"type":"object","additionalProperties":false,"properties":{"index":{"type":"string"},"total":{"type":"integer","format":"uint64","minimum":0,"description":"How many entities match in all, counted exactly however many they are."},"engine_ms":{"type":"integer","format":"uint64","minimum":0,"description":"The engine's own time for the search, in milliseconds. It leaves out parsing the filter, so it is no budget."}},"required":["index","total","engine_ms"],"description":"What the engine answered for one search."},"CountedFacet":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"field":{"$ref":"#/components/schemas/FieldCode"},"search":{"type":"integer","format":"uint","minimum":0,"description":"The search of the plan that counted it, counted from 0."},"values":{"type":"integer","format":"uint","minimum":0,"description":"The values shown."},"limit":{"type":"integer","format":"uint32","minimum":0,"description":"The most values the facet carries before `more`: the layout's `limit`, or the limit `facet_values`. None for a\nfacet without a cap: one the request named, and every kind but a list or a swatch."},"more":{"type":"integer","format":"uint64","minimum":0,"description":"How many values with results the cap left out."},"kept":{"type":"array","items":{"type":"string"},"description":"Chosen values the results do not hold, shown with a count of 0 so they can be unchosen."},"upper_bound":{"description":"Why the counts are upper bounds, when they are.","$ref":"#/components/schemas/UpperBound"},"histogram":{"description":"The bins of a histogram, which only approximate the distribution. The same however the facet came to be counted,\nby the page or by a request that named it; a histogram the page left out is in `deferred` of the step instead.","$ref":"#/components/schemas/CountedBins"}},"required":["type","field","search","values"],"description":"How one facet of a section was counted."},"UpperBound":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"fields":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"}},"by":{"type":"string","const":"multi_select"}},"required":["by","fields"],"description":"Several values are chosen in these variant-level fields, and a tile may hold a variant for each: the counts\nadd them up, at most as high as the tiles that have the value in some variant matching the other choices."},{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldCode"},"by":{"type":"string","const":"free_range"}},"required":["by","field"],"description":"A free range over this variant-level field narrows the results beside a choice in another variant-level field,\nand one variant may meet the range while another meets the choice."}],"description":"Why a facet's counts can be higher than the tiles a shopper would find. Every other count is exact."},"CountedBins":{"type":"object","additionalProperties":false,"properties":{"bins":{"type":"integer","format":"uint","minimum":0,"description":"How many bins the response lists, those without tiles included."},"from":{"type":"number","description":"Where the first bin starts: the ladder's number at or below the cheapest price."},"to":{"type":"number","description":"Where the last bin ends, exclusive: the ladder's number above the dearest."}},"required":["bins","from","to"],"description":"How a histogram's bars were drawn, which makes them an approximation of the distribution: the bins are a fixed\nladder of preferred numbers, each 8 to 15 percent above the one before and the same whatever the catalog holds, so a\nbar says where products sit, not exactly how many. A tile with several prices counts in every bin one of them falls\nin, so the bars may add up to more than the tiles, and the chosen range may cut a bin in two. Each count itself\nfollows the rules of any other facet's."},"CountedGroup":{"type":"object","additionalProperties":false,"properties":{"group":{"$ref":"#/components/schemas/GroupCode"},"facets":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"},"description":"Its facets as the page lists them, in the group's order."},"counted":{"type":"boolean","description":"The page counted every facet of it, or none: it did not count the group."},"reason":{"type":"string","description":"Why, as a sentence, such as \"tyre_size stands at position 7 of the page, within the first 30 facets it counts\"."}},"required":["group","facets","counted","reason"],"description":"How a group of facets was counted: all of them or none, since a finder with a facet left out would draw an empty\nselect, and a group is one control that a shopper opens or reads at once."},"RankedSection":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"hits":{"type":"array","items":{"$ref":"#/components/schemas/RankedHit"}}},"required":["type","hits"],"description":"The hits of one section and why each sits where it does."},"RankedHit":{"type":"object","additionalProperties":false,"properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"variant":{"$ref":"#/components/schemas/EntityId"},"position":{"type":"integer","format":"uint32","minimum":0,"description":"Its place on the page, counted from 1."},"pinned":{"description":"The rule whose pin put it here.","$ref":"#/components/schemas/RuleId"},"sponsored":{"description":"The campaign of that pin, when it is a paid one.","$ref":"#/components/schemas/Sponsored"},"weighed":{"type":"array","items":{"$ref":"#/components/schemas/Weight"},"description":"The boosts and buries that weighed it, each with the factor it gave."},"criteria":{"type":"array","items":{"$ref":"#/components/schemas/Criterion"},"description":"What the engine compared it by, in the order it compares, its business score among them."},"matched":{"type":"array","items":{"$ref":"#/components/schemas/MatchedWord"},"description":"The query's words it was found by, each with the fields it was found in and the synonym it was found through."},"signals":{"type":"array","items":{"$ref":"#/components/schemas/SignalScore"},"description":"The signals of its business score with their weights, the one that adds the most first."},"why":{"type":"array","items":{"$ref":"#/components/schemas/HitReason"},"description":"Why it sits here, from the facts above and the rest of the trace, in the order the reasons act."}},"required":["entity","position"],"description":"One hit and what placed it."},"Weight":{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"effect":{"$ref":"#/components/schemas/EffectKind"},"factor":{"type":"number"}},"required":["rule","effect","factor"],"description":"A boost or bury that weighed a hit."},"Criterion":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"matched":{"type":"integer","format":"uint32","minimum":0},"of":{"type":"integer","format":"uint32","minimum":0},"criterion":{"type":"string","const":"words"}},"required":["criterion","matched","of"],"description":"How many of the query's words it was found by, as typed, through a synonym or with a typo; more come first."},{"type":"object","additionalProperties":false,"properties":{"typos":{"type":"integer","format":"uint32","minimum":0},"criterion":{"type":"string","const":"typos"}},"required":["criterion","typos"],"description":"How many typos it took to find those words; fewer come first."},{"type":"object","additionalProperties":false,"properties":{"sort":{"$ref":"#/components/schemas/SortCode"},"by":{"$ref":"#/components/schemas/FieldPath"},"direction":{"$ref":"#/components/schemas/SortDirection"},"value":true,"criterion":{"type":"string","const":"sort"}},"required":["criterion","sort","by","direction"],"description":"The sort the request chose, and the hit's value in it; a hit without a value comes last."},{"type":"object","additionalProperties":false,"properties":{"score":{"type":"number"},"criterion":{"type":"string","const":"business_score"}},"required":["criterion","score"],"description":"Its business score from 0 to 1; higher comes first."},{"type":"object","additionalProperties":false,"properties":{"score":{"type":"number"},"criterion":{"type":"string","const":"proximity"}},"required":["criterion","score"],"description":"How close together the query's words stand in it, from 0 to 1; closer comes first."},{"type":"object","additionalProperties":false,"properties":{"score":{"type":"number"},"criterion":{"type":"string","const":"fields"}},"required":["criterion","score"],"description":"How early among the type's searched fields, and how early in a field, the words were found, from 0 to 1."},{"type":"object","additionalProperties":false,"properties":{"exact":{"$ref":"#/components/schemas/Exact"},"score":{"type":"number"},"criterion":{"type":"string","const":"exactness"}},"required":["criterion","exact","score"],"description":"Whether a field holds the query as a whole, starts with it, or neither, from 0 to 1."}],"description":"One criterion the engine compared hits by, with this hit's value. Hits are compared criterion by criterion in the\norder the engine ranks by, and the first criterion two hits differ in decides which comes first."},"FieldPath":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}(\\.[a-z][a-z0-9_]{0,63})?$","description":"A field of an entity as search and result settings name it: a core field such as `title`, or an attribute as `attributes.<code>`; sort options also read `signals.<code>`."},"SortDirection":{"type":"string","enum":["asc","desc"]},"Exact":{"oneOf":[{"type":"string","const":"whole","description":"A field holds exactly the query."},{"type":"string","const":"start","description":"A field starts with the query."},{"type":"string","const":"no","description":"No field holds the query as it was written."}]},"MatchedWord":{"type":"object","additionalProperties":false,"properties":{"word":{"type":"string","description":"The word as the engine reads the query."},"synonym":{"type":"string","description":"The synonym it was found through, when the hit holds the synonym and not the word itself."},"entry":{"description":"The entry of `synonyms.json` that leads the word to its synonym, such as `de/groups/0`; with `synonym` only.","$ref":"#/components/schemas/SynonymId"},"typo":{"type":"string","description":"The word of the hit it was found by with a typo, as its field writes it, such as `Bremsbelag` for `bremsbelg`."},"fields":{"type":"array","items":{"type":"string"},"description":"The searched fields it was found in, in the type's order, such as `title`; `words` holds what the index adds,\nsuch as the titles of its categories."}},"required":["word","fields"],"description":"A word of the query and how the hit was found by it."},"SignalScore":{"type":"object","additionalProperties":false,"properties":{"signal":{"$ref":"#/components/schemas/SignalCode"},"value":{"description":"The signal as the catalog sends it, when it does."},"score":{"type":"number","description":"Normalized to 0..1, the better the higher."},"weight":{"type":"number","description":"Its weight in the type's business score."}},"required":["signal","score","weight"],"description":"One signal of a hit's business score."},"SignalCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"The code of a signal, such as `sales_30d` or `rating`."},"HitReason":{"type":"object","additionalProperties":false,"properties":{"sentence":{"type":"string","description":"The reason in a sentence, such as \"Pinned to position 1 by the rule “Sommer-Sale”.\""},"because":{"$ref":"#/components/schemas/Because"}},"required":["sentence","because"],"description":"One reason a hit sits where it does, in a sentence and as data."},"Because":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string"},"position":{"type":"integer","format":"uint32","minimum":0},"sponsored":{"$ref":"#/components/schemas/Sponsored"},"kind":{"type":"string","const":"pin"}},"required":["kind","rule","description","position"],"description":"A rule pinned it to its place, which comes before everything else; a paid pin names its campaign, and the\nsentence calls it a sponsored placement of that campaign."},{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string"},"effect":{"$ref":"#/components/schemas/EffectKind"},"factor":{"type":"number"},"kind":{"type":"string","const":"weight"}},"required":["kind","rule","description","effect","factor"],"description":"A rule's boost or bury weighed it against the hits beside it."},{"type":"object","additionalProperties":false,"properties":{"words":{"type":"string"},"as":{"$ref":"#/components/schemas/Selector"},"entry":{"type":"string","description":"Where the meaning comes from, such as \"the alias \\\"graues\\\" of the color group Grau\"."},"rule":{"$ref":"#/components/schemas/RuleId"},"pattern":{"type":"string"},"synonym":{"description":"The entry of `synonyms.json` that let the words name it, such as `de/groups/0`, when a synonym did.","$ref":"#/components/schemas/SynonymId"},"kind":{"type":"string","const":"named"}},"required":["kind","words","as","entry"],"description":"Words of the query named something it is or has, which narrowed the results to it."},{"type":"object","additionalProperties":false,"properties":{"word":{"type":"string"},"synonym":{"type":"string"},"entry":{"$ref":"#/components/schemas/SynonymId"},"kind":{"type":"string","const":"synonym"}},"required":["kind","word","synonym"],"description":"A word of the query found it only through a synonym, which the entry of `synonyms.json` holds."},{"type":"object","additionalProperties":false,"properties":{"matched":{"type":"integer","format":"uint32","minimum":0},"of":{"type":"integer","format":"uint32","minimum":0},"typos":{"type":"integer","format":"uint32","minimum":0},"fields":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"text"}},"required":["kind","matched","of","typos","fields"],"description":"How well it matches the query's text, and where."},{"type":"object","additionalProperties":false,"properties":{"sort":{"$ref":"#/components/schemas/SortCode"},"by":{"$ref":"#/components/schemas/FieldPath"},"direction":{"$ref":"#/components/schemas/SortDirection"},"value":true,"kind":{"type":"string","const":"sort"}},"required":["kind","sort","by","direction"],"description":"The sort the request chose orders the page."},{"type":"object","additionalProperties":false,"properties":{"score":{"type":"number"},"signal":{"$ref":"#/components/schemas/SignalScore"},"kind":{"type":"string","const":"business_score"}},"required":["kind","score"],"description":"Its business score orders it among the hits that match as well as it does; `signal` adds the most to it."}]},"AssembledSection":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"total":{"type":"integer","format":"uint64","minimum":0},"upper_bound":{"description":"Why the total is an upper bound, when it is.","$ref":"#/components/schemas/UpperBound"},"hits":{"type":"integer","format":"uint","minimum":0,"description":"How many hits this page shows."}},"required":["type","total","hits"],"description":"One section of the response."},"SuggestedText":{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string"},"scope":{"$ref":"#/components/schemas/CategoryId"},"source":{"type":"string","description":"The entry it completes, as a sentence, such as \"the word \\\"Ecksofa\\\" of product titles, whose head is the category\nSofas\"."},"products":{"type":"integer","format":"uint64","minimum":0,"description":"The products that hold it, which rank it; for a scoped suggestion, those in its category."}},"required":["text","source","products"],"description":"One suggestion and where it comes from."},"Timings":{"type":"object","additionalProperties":false,"properties":{"total_ms":{"type":"number","format":"double","description":"The whole time in milliseconds, the engine round trips included."},"round_trip_ms":{"type":"number","format":"double","description":"The engine round trips together, in milliseconds: every call the request sent, a relaxed search's first one\nincluded."},"own_ms":{"type":"number","format":"double","description":"The Query API's own share in milliseconds: the whole time without the engine round trips."},"before_engine_ms":{"type":"number","format":"double","description":"The Query API's own time before its first engine call: reading the request, understanding the query and\nplanning the searches. The rest of `own_ms` comes after the engine answered. A request that asks the engine\nnothing has none."},"total_budget_ms":{"type":"integer","format":"uint32","minimum":0,"description":"The budget of the whole time for the request's kind: autocomplete's, or that of a search and a page."},"own_budget_ms":{"type":"integer","format":"uint32","minimum":0,"description":"The budget of the Query API's own share."}},"required":["total_ms","round_trip_ms","own_ms","total_budget_ms","own_budget_ms"],"description":"How long the Query API took for a request, as the server measured it: from the request's arrival to its assembled\nanswer, so writing the answer and the network path to the client come on top. Each time is held against the p95\nbudget of its kind: one request above it is a warning, and the budget is missed when more than 5 of 100 are. Only\nthe server measures, so a trace assembled anywhere else, such as in a test, has none."},"Problem":{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","description":"A URI naming the kind of problem."},"title":{"type":"string","description":"The status's own phrase, such as `Bad Request`."},"status":{"type":"integer","format":"uint16","minimum":0,"maximum":65535,"description":"The answer's HTTP status."},"detail":{"type":"string","description":"What happened and what to do next, for the person who can fix it."},"violations":{"type":"array","items":{"$ref":"#/components/schemas/Violation"},"description":"For invalid input, every value that is wrong."}},"required":["type","title","status"],"description":"An error of either API. Its `type` is always `about:blank`, so the `status` tells problems apart."},"Violation":{"type":"object","additionalProperties":false,"properties":{"file":{"type":"string","description":"The file, such as `rules.json`, when the input is a release."},"pointer":{"type":"string","description":"A JSON pointer to the value, such as `/rules/3/then/pin/0/entity`."},"message":{"type":"string","description":"What is wrong, as a sentence."}},"required":["pointer","message"],"description":"One thing that is wrong with the input, and where."},"ProductResponse":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that answered."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog the product was read from."},"product":{"$ref":"#/components/schemas/Product"},"trace":{"description":"Why the response is what it is; only when the request asked for it.","$ref":"#/components/schemas/ProductTrace"}},"required":["release","snapshot","product"],"description":"The answer of a product lookup."},"Product":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/EntityId"},"title":{"type":"string"},"url":{"type":"string","description":"The merchant's URL of the product in the locale."},"alternates":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$":{"type":"string"}},"description":"The product's URL in each other locale of the channel, for a language switch and `hreflang`."},"description":{"type":"string"},"brand":{"type":"string"},"badges":{"type":"array","items":{"type":"string"},"description":"The badges the release's overlays give the product, in the locale, such as \"Bestseller\"."},"signals":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"$ref":"#/components/schemas/Scalar"}},"description":"The signals the type returns as result fields, such as its rating and review count: those its tile shows."},"images":{"type":"array","items":{"type":"string"},"description":"Every photo of the product, its own first, then those of its variants."},"categories":{"type":"array","items":{"$ref":"#/components/schemas/CategoryId"},"description":"The categories the product is assigned to, the primary one first."},"offer":{"description":"What a shopper can buy best, as the product's tile shows it when one tile stands for the whole product: the\nvariants with the best availability, and among them the lowest price paid and the soonest delivery.","$ref":"#/components/schemas/ProductOffer"},"details":{"type":"array","items":{"$ref":"#/components/schemas/ProductDetail"},"description":"What holds for the whole product, in the order the type's `details` list them: its own values and those every\nvariant shares."},"variants":{"type":"array","items":{"$ref":"#/components/schemas/ProductVariant"},"description":"The variants sold in the channel, in the catalog's order; none for a product the catalog sends without."}},"required":["id","title"],"description":"A product as its page shows it, in the request's channel and locale."},"ProductOffer":{"type":"object","additionalProperties":false,"properties":{"price":{"type":"number"},"sale_price":{"type":"number","description":"The reduced price, while a sale is on."},"currency":{"$ref":"#/components/schemas/Currency"},"availability":{"$ref":"#/components/schemas/Availability"},"stock":{"type":"integer","format":"uint64","minimum":0,"description":"How many are in stock, where the catalog says."},"delivery_days":{"$ref":"#/components/schemas/DeliveryDays"}},"required":["currency"],"description":"An offer in the request's channel, with the sale price that holds at the time the live index run was built; the\nindexer builds again when a sale window opens or closes."},"Availability":{"type":"string","enum":["in_stock","out_of_stock","preorder","backorder"]},"DeliveryDays":{"type":"object","additionalProperties":false,"properties":{"min":{"type":"integer","format":"uint16","minimum":0,"maximum":65535},"max":{"type":"integer","format":"uint16","minimum":0,"maximum":65535}},"required":["min","max"],"description":"How many days delivery takes, from the earliest to the latest."},"ProductDetail":{"type":"object","additionalProperties":false,"properties":{"attribute":{"$ref":"#/components/schemas/AttributeCode"},"label":{"type":"string","description":"The attribute's label in the locale."},"values":{"type":"array","items":{"$ref":"#/components/schemas/DetailValue"},"description":"Its values in the attribute's unit: an option by its label, a number rounded to the attribute's precision, `true`\nor `false`, a date or text, or a range's bounds. Several for a list, such as two materials."},"unit":{"description":"The unit of a quantity's or a range's numbers, such as `cm`.","$ref":"#/components/schemas/Unit"},"swatch":{"type":"string","description":"The swatch color of an option's one value, or of the group it shows under, as its facet draws it; none for a\ndetail of several values."},"image":{"type":"string","description":"A swatch image of an option's one value, such as a pattern no single color shows; its URL."}},"required":["attribute","label","values"],"description":"One detail of a product or variant, as the shopper's locale reads it."},"AttributeCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"The code of an attribute definition, such as `color` or `seat_height`. The reserved words `any`, `all`, `not`, `category`, `brand`, `price`, `on_sale`, `availability`, `delivery_days` and `rating` cannot be attribute codes.","not":{"enum":["any","all","not","category","brand","price","on_sale","availability","delivery_days","rating"]}},"DetailValue":{"anyOf":[{"$ref":"#/components/schemas/Scalar"},{"$ref":"#/components/schemas/RangeValue"}],"description":"A detail's value: one scalar, or a range's bounds."},"RangeValue":{"type":"object","additionalProperties":false,"properties":{"min":{"type":"number"},"max":{"type":"number"}},"description":"A range's bounds, such as an age of 3 to 6 years; either may be open."},"ProductVariant":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/EntityId"},"title":{"type":"string","description":"The variant's own title, where the catalog gives it one."},"url":{"type":"string","description":"The variant's own URL in the locale, where the catalog gives it one."},"images":{"type":"array","items":{"type":"string"},"description":"The variant's own photos."},"details":{"type":"array","items":{"$ref":"#/components/schemas/ProductDetail"},"description":"What sets the variant apart: its values that differ between the product's variants, such as its color and\nsize, in the order of the product's details."},"offer":{"$ref":"#/components/schemas/ProductOffer"}},"required":["id"],"description":"A buyable variation of a product."},"ProductTrace":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId"},"snapshot":{"$ref":"#/components/schemas/SnapshotId"},"publishing":{"description":"A release was being published while this request was answered; the product may be the new release's already.","$ref":"#/components/schemas/ReleaseId"},"live":{"description":"The index run this was answered from and how it took the release live.","$ref":"#/components/schemas/WentLive"},"derived":{"type":"array","items":{"type":"string"},"description":"The release's files the live run derived from its catalog because the release leaves them out, such as\n`types.json`, whose `details` say which details the product lists."},"request":{"$ref":"#/components/schemas/ProductRequest","description":"The request as it was answered, with the channel and locale it was answered in."},"index":{"type":"string","description":"The engine index the product was read from, which the index run compiled for lookups."},"filter":{"type":"string","description":"The engine filter that found it."},"round_trip_ms":{"type":"number","format":"double","description":"From sending the lookup to reading its answer, in milliseconds."}},"required":["release","snapshot","request","index","filter","round_trip_ms"],"description":"Why a product lookup answered what it did."},"ProductRequest":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/EntityId"},"url":{"type":"string"},"channel":{"$ref":"#/components/schemas/ChannelId"},"locale":{"$ref":"#/components/schemas/Locale"}},"required":["channel","locale"],"description":"A product lookup as it was answered."},"PageResponse":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that decided."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog the paths were read from."},"type":{"$ref":"#/components/schemas/PageType"},"status":{"type":"integer","format":"uint16","minimum":0,"maximum":65535,"description":"The HTTP status the storefront answers with: 200, a redirect's 301, 302, 307 or 308, 404 or 410."},"search":{"type":"string","description":"The search page's path in this locale, as `routes.json` names it, where the page's search box submits to."},"location":{"type":"string","description":"Where a redirect leads: always the final URL, never a chain."},"canonical":{"type":"string","description":"The URL a search engine should keep for this page; none where the page is `noindex` or not found."},"robots":{"description":"What search engines may do with the page; none for a redirect, a page that is gone or not found.","$ref":"#/components/schemas/Robots"},"entity":{"description":"The category, brand or product the page is the page of.","$ref":"#/components/schemas/EntityRef"},"breadcrumbs":{"type":"array","items":{"$ref":"#/components/schemas/Breadcrumb"},"description":"A category page's ancestors and itself, from the root, for the trail above the page and its `BreadcrumbList`."},"from":{"type":"string","description":"The text a search typed that led to this category page, so the page can say what it shows and lead back."},"request":{"description":"The search a listing runs, with the filters, sort and page its URL holds.","$ref":"#/components/schemas/SearchRequest"},"results":{"description":"The listing's results, as `POST /v1/search` answers its `request`; left out with `results=0`. With `debug`, a\nredirect the listing's search decided keeps them too, so its trace says which rule or resolution sent it on.","$ref":"#/components/schemas/SearchResponse"},"product":{"description":"A product page's product, as `GET /v1/product` shows it; left out with `results=0`.","$ref":"#/components/schemas/Product"},"trace":{"description":"Why the page is what it is; only when the request asked for it.","$ref":"#/components/schemas/PageTrace"}},"required":["release","snapshot","type","status","search"],"description":"What a URL is, and everything a storefront needs to answer it."},"PageType":{"oneOf":[{"type":"string","const":"category","description":"A category's listing."},{"type":"string","const":"brand","description":"A brand's listing."},{"type":"string","const":"search","description":"The search page."},{"type":"string","const":"product","description":"A product's page."},{"type":"string","const":"redirect","description":"The URL lives elsewhere: `location` says where."},{"type":"string","const":"gone","description":"The URL was removed on purpose."},{"type":"string","const":"not_found","description":"Not a page OrbSearch knows; a shop with other pages answers it with its own routes first."}]},"Robots":{"oneOf":[{"type":"string","const":"index,follow","description":"One of the pages the merchant declared indexable."},{"type":"string","const":"noindex,follow","description":"A page the merchant did not choose, such as a filter state or the search page; its links are still followed."}],"description":"What search engines may do with a page."},"Breadcrumb":{"type":"object","additionalProperties":false,"properties":{"title":{"type":"string"},"url":{"type":"string"}},"required":["title","url"],"description":"One step of a page's trail."},"PageTrace":{"type":"object","additionalProperties":false,"properties":{"channel":{"$ref":"#/components/schemas/ChannelId","description":"The channel and locale the URL was resolved in."},"locale":{"$ref":"#/components/schemas/Locale"},"path":{"type":"string","description":"The path as it is matched: percent-decoded, in lowercase, without duplicate or trailing slashes."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/PageStep"}},"timings":{"description":"How long the Query API took for the whole page, its listing's search or its product included.","$ref":"#/components/schemas/Timings"}},"required":["channel","locale","path","steps"],"description":"How a URL was resolved: each step of the order with what it found; the last one decided."},"PageStep":{"type":"object","additionalProperties":false,"properties":{"step":{"$ref":"#/components/schemas/ResolveStep"},"outcome":{"type":"string","description":"What the step found, in a sentence."}},"required":["step","outcome"]},"ResolveStep":{"oneOf":[{"type":"string","const":"legacy","description":"The legacy table, `redirects.json`: its exact entries, then its patterns."},{"type":"string","const":"search","description":"The search page's path."},{"type":"string","const":"catalog","description":"The paths of the catalog's categories and brands."},{"type":"string","const":"normalize","description":"The path written differently from the page's own, such as in another case or with a trailing slash."},{"type":"string","const":"parameters","description":"The URL's parameters read as filters, sort and page."},{"type":"string","const":"product","description":"A product by its URL."},{"type":"string","const":"robots","description":"Whether search engines may index the page, and its canonical."},{"type":"string","const":"results","description":"What the results change: an empty page, a page past the last, a search that leads elsewhere."}],"description":"The order a URL is resolved in, then what decides about the page once it is known."},"RobotsLines":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId"},"lines":{"type":"array","items":{"type":"string"},"description":"`Disallow` lines in the pattern form Google documents, to place under the shop's `User-agent: *`."}},"required":["release","lines"],"description":"The `robots.txt` lines the merchant serves from their own origin: they keep crawlers off every filter\nstate no indexable page uses, off any URL with a second parameter, and off the search page."},"Events":{"type":"object","additionalProperties":false,"properties":{"page_view":{"description":"The page the events happened on, as its searches named it.","$ref":"#/components/schemas/PageViewId"},"traffic":{"$ref":"#/components/schemas/Traffic","description":"Whose events these are, as the searches said. Default: `shopper`."},"events":{"type":"array","items":{"$ref":"#/components/schemas/Event"},"description":"One or more, up to the limit `events_per_call`.","minItems":1,"maxItems":100}},"required":["events"],"description":"The events of one page load, as a storefront sends them: a click at once, views together when the page is hidden."},"Event":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/EventKind"},"query_id":{"$ref":"#/components/schemas/QueryId","description":"The query ID of the answer whose first page the shopper saw: a hit that a later page brought names the search\nit continues. The Query API takes it for `LIMITS.event_hours` after the answer; the id says when that was."},"entity":{"description":"The hit, such as `product:SOFA-LUND-3`. Its type names the section it stood in.","$ref":"#/components/schemas/EntityRef"},"placement":{"description":"The banner, by the rule that placed it.","$ref":"#/components/schemas/RuleId"},"position":{"type":"integer","format":"uint32","minimum":1,"description":"Where the hit stood, counted from 1: in the listed section across the pages, as a grid banner's position counts,\nand in a section beside it among the hits it shows, up to the limit `reachable_hits`. Left out for a banner.","maximum":1000},"campaign":{"description":"The campaign of a sponsored hit, as the answer names it.","$ref":"#/components/schemas/Campaign"},"surface":{"$ref":"#/components/schemas/EventSurface","description":"Who acted. Default: `storefront`."}},"required":["type","query_id"],"description":"A click on or a view of one hit or banner of an answer. It names the hit by its entity or the banner by its rule,\nnever both."},"EventKind":{"oneOf":[{"type":"string","const":"click","description":"The shopper opened the hit or followed the banner."},{"type":"string","const":"view","description":"Half of the hit or banner was visible for a second. A storefront reports one per query ID and hit or banner."}]},"EventSurface":{"oneOf":[{"type":"string","const":"storefront","description":"The shopper, on the storefront's page."},{"type":"string","const":"agent","description":"An agent acting for the shopper through a tool the storefront offers, such as a WebMCP tool call."}],"description":"Where an event happened."},"EventsTaken":{"type":"object","additionalProperties":false,"properties":{"accepted":{"type":"integer","format":"uint32","minimum":0},"late":{"type":"integer","format":"uint32","minimum":0,"description":"Events whose answer is older than `LIMITS.event_hours`, refused by their query ID's time alone; the report\ncounts them as late."}},"required":["accepted","late"],"description":"What the Query API took of a batch. It records events after it answered, as it does searches: what it takes shows\nin the report after the next roll-up."},"ReleaseFiles":{"type":"object","additionalProperties":false,"properties":{"files":{"type":"object","additionalProperties":{"type":"string"}},"replaces":{"type":"object","additionalProperties":{"type":"string"},"description":"For a write, the files it replaces. Every option value and group then keeps its slug: one the write changes\nkeeps the old slug among its aliases, one the write leaves out keeps the slug it had, and one without any gets\none made from its label, which is stored from then on."}},"required":["files"],"description":"A release as files, by file name, such as `{ \"rules.json\": \"{ \\\"rules\\\": [] }\" }`. A file left out is empty."},"ValidatedRelease":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/ReleaseId"},"files":{"type":"object","additionalProperties":{"type":"string"}}},"required":["id","files"],"description":"A valid release: its id, named by its content, and its files in canonical form. Files without content are left\nout."},"ExportToInspect":{"type":"object","additionalProperties":false,"properties":{"snapshot":{"$ref":"#/components/schemas/SnapshotId"},"files":{"type":"object","additionalProperties":{"type":"string"}},"sample":{"type":"integer","format":"uint64","minimum":1,"description":"Map only the first rows of a file larger than 8 MB; the columns and the mapping still describe the whole file."}},"required":["snapshot","files"],"description":"A kept export and the release files to read it with, by name, as `ValidateRelease` takes them."},"Inspection":{"type":"object","additionalProperties":false,"properties":{"feed":{"$ref":"#/components/schemas/FeedReport","description":"How the file was read and who placed its columns, with the columns whose values go nowhere."},"rows":{"type":"integer","format":"uint64","minimum":0,"description":"Rows in the file, the header and blank rows not counted."},"sample":{"type":"integer","format":"uint64","minimum":0,"description":"When the products, rejections and values cover the first rows only: how many it mapped. Left out when they\ncover the whole file. The columns and the mapping always describe the whole file."},"columns":{"type":"array","items":{"$ref":"#/components/schemas/InspectedColumn"},"description":"Every column of the file, in the file's order; empty for OrbSearch's own JSON Lines."},"mapping":{"$ref":"#/components/schemas/Feed","description":"The mapping as `feed.json` holds it: the release's own, or the derived one, ready to be saved and corrected."},"defaults":{"$ref":"#/components/schemas/Defaults","description":"The release files the release leaves out, derived from the export, each setting with its reason. Attribute\ndefinitions, types and channels describe the whole file, except an attribute a correction changed after the\nexport's first look, which the rows mapped describe; facets, sorting and ranking describe the rows mapped."},"products":{"type":"integer","format":"uint64","minimum":0,"description":"Products the rows become."},"variants":{"type":"integer","format":"uint64","minimum":0,"description":"Their variants; a product without variants counts none."},"categories":{"type":"integer","format":"uint64","minimum":0,"description":"Categories made from the category paths."},"preview":{"type":"array","items":{"$ref":"#/components/schemas/Entity"},"description":"The first products as they would be indexed, their values read as their definitions say."},"tiles":{"type":"array","items":{"$ref":"#/components/schemas/Hit"},"description":"The same products as a search answers them in the first channel and its first locale: the hits a storefront\ndraws as tiles. Empty while the release has no channel."},"details":{"type":"array","items":{"$ref":"#/components/schemas/InspectedDetail"},"description":"Every product detail shoppers could meet: the attributes the products carry and the fields every shop filters\nby, each a filter, found by search or not used, as the release completed with the derived files makes it. The\nfilters come first, in the order pages show them; their values cover the sample, as the products do."},"violations":{"type":"array","items":{"$ref":"#/components/schemas/Violation"},"description":"What the release, completed with what this export derives, does not hold, such as a filter `categories.json`\nnames over a column this export no longer has. The index run would fail with the same sentences."},"rejected":{"type":"integer","format":"uint64","minimum":0,"description":"Rows that would be left out."},"rejections":{"type":"array","items":{"$ref":"#/components/schemas/RejectionGroup"},"description":"Those rows grouped by what is wrong, the most common first."},"values":{"$ref":"#/components/schemas/ValuesReport","description":"What the import would make of the attribute values."}},"required":["feed","rows","mapping","products","variants","categories","preview","rejected"],"description":"An export as the import reads it: its format, every column with its samples and where it goes and why, what the\nrows become, the first products as they would be indexed, and what would be left out."},"FeedReport":{"type":"object","additionalProperties":false,"properties":{"format":{"$ref":"#/components/schemas/Format","description":"The file as the import read it: its kind and, for text and spreadsheets, its encoding, delimiter, sheet and\nheader row."},"mapping":{"$ref":"#/components/schemas/MappingSource"},"unmapped":{"type":"array","items":{"type":"string"},"description":"Columns `feed.json` does not name; their values were left out."},"ignored":{"type":"array","items":{"type":"string"},"description":"Columns left out on purpose: mapped to `ignore`, or by the derived mapping as shipping, tax and ads data, or\nbecause no row fills them."}},"required":["format","mapping"],"description":"How an export was read and mapped: what the import recognized, whether `feed.json` or the derived mapping placed\nthe columns, and the columns whose values went nowhere."},"Format":{"type":"object","additionalProperties":false,"properties":{"kind":{"$ref":"#/components/schemas/FormatKind"},"delimiter":{"description":"Delimited text: the character between cells.","$ref":"#/components/schemas/Delimiter"},"encoding":{"description":"Text: the character encoding.","$ref":"#/components/schemas/Encoding"},"decimal":{"description":"The decimal mark of numbers written as text, such as `,` in \"1.099,00\". Without it, each column's numbers tell.","$ref":"#/components/schemas/Decimal"},"sheet":{"type":"string","description":"Spreadsheets: the sheet to read by its name. Default: the first."},"header":{"type":"integer","format":"uint32","minimum":0,"description":"Text and spreadsheets: the row holding the column names, counted from 1. Without it, the first row that looks\nlike a header is taken, so title rows above it are skipped."}},"description":"How a file is read: its kind, and for text and spreadsheets the details a guess can get wrong. The import\nrecognizes each from the bytes; a value here pins it."},"FormatKind":{"oneOf":[{"type":"string","const":"jsonl","description":"OrbSearch's own catalog: one entity per line as JSON. It needs no mapping."},{"type":"string","const":"csv","description":"Delimited text: CSV, TSV and TXT, Merchant Center's TSV included."},{"type":"string","const":"xlsx","description":"An Excel workbook."},{"type":"string","const":"xml","description":"A Merchant Center feed: RSS 2.0 or Atom with the `g:` namespace."},{"type":"string","const":"json","description":"JSON records of a shape of their own, as an array or one object per line: nested objects are read as columns\nsuch as `dimensions.width`, arrays of plain values as lists."}],"description":"The kinds of files the import reads. Any of them may arrive gzipped."},"Delimiter":{"type":"string","enum":[",",";","\t","|"]},"Encoding":{"oneOf":[{"type":"string","enum":["utf-8","utf-16le","utf-16be"]},{"type":"string","const":"windows-1252","description":"What Excel on Windows writes for German text; ISO 8859-1 reads the same."}]},"Decimal":{"type":"string","enum":[",","."]},"MappingSource":{"oneOf":[{"type":"string","const":"derived","description":"The release has no `feed.json`; the import derived the mapping from the columns and their values."},{"type":"string","const":"file","description":"The release's `feed.json`, exactly as written."},{"type":"string","const":"native","description":"OrbSearch's own JSON Lines, which need no mapping."}],"description":"Who placed an export's columns."},"InspectedColumn":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","description":"The column's name as the header writes it."},"filled":{"type":"integer","format":"uint64","minimum":0,"description":"Rows with a value in it."},"samples":{"type":"array","items":{"type":"string"},"description":"Its first different values as written, up to five."},"mapping":{"description":"Where its values go; left out when they go nowhere.","$ref":"#/components/schemas/Column"},"reason":{"$ref":"#/components/schemas/Reason"},"proposed":{"description":"Where `feed.json` placed the column: where the import itself would send it, and why. Beside a merchant's change,\nit says what going back to the proposal means.","$ref":"#/components/schemas/Proposal"}},"required":["name","filled","reason"],"description":"One column of an export: what it holds and where its values go."},"Column":{"anyOf":[{"$ref":"#/components/schemas/Target"},{"$ref":"#/components/schemas/ColumnRead"}],"description":"Where a column's values go: a target such as `\"title\"` or `\"attributes.color\"`, or a target with the options\nthat say how to read the cells."},"Target":{"type":"string","pattern":"^(id|group|title|description|url|image|images|brand|categories|keywords|price|sale_price|sale_window|currency|availability|stock|delivery_days|attributes|attributes\\.\\*|ignore|(attributes|signals)\\.[a-z][a-z0-9_]{0,63})$","description":"Where a column's values go: a field such as title or price, attributes.<code>, attributes.* (the code made from what the column name's * matched), attributes (with name or pairs), signals.<code>, or ignore."},"ColumnRead":{"type":"object","additionalProperties":false,"properties":{"to":{"$ref":"#/components/schemas/Target"},"split":{"description":"Lists: the text between the values of one cell, such as `,` in \"bild1.jpg,bild2.jpg\" or `|` in \"Holz|Metall\";\nor several, any of which separates them, such as `[\"|\", \";\"]` in \"Blau;Schwarz|Grau\".","$ref":"#/components/schemas/OneOrMany_string"},"levels":{"type":"string","description":"Categories: the text between the levels of one path, such as ` > ` in \"Wohnen > Sofas\"."},"values":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Availability"},"description":"Availability: the shop's words for each state, such as `{ \"sofort lieferbar\": \"in_stock\" }`."},"unit":{"description":"Attributes: the unit of plain numbers in the column, such as `cm` for a column \"Breite\" holding \"218\".","$ref":"#/components/schemas/Unit"},"locale":{"description":"Texts, categories and localized attributes: the locale the column is written in. Default: the source locale,\nthe first channel's first locale.","$ref":"#/components/schemas/Locale"},"channel":{"description":"Offer fields: the channel the column's prices or stock belong to. Default: the first channel in channels.json.","$ref":"#/components/schemas/ChannelId"},"name":{"type":"string","description":"Attributes written as name and value in two columns: the column holding each value's attribute name, with the\nsame `*` as this column's name, such as `\"Attribute * name\"` beside `\"Attribute * value(s)\"`."},"pairs":{"type":"string","description":"Attributes written as pairs in one cell, such as \"Farbe: Rot | Breite: 218 cm\": the text between the pairs."},"assign":{"type":"string","description":"With `pairs`: the text between an attribute's name and its value. Default: `:`."}},"required":["to"],"description":"A column's target with the options that say how its cells are read. The list is closed on purpose: no templates,\nexpressions or combined columns until a real feed needs one."},"OneOrMany_string":{"description":"One value, or a list meaning any of them.","anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1}]},"Reason":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"pattern":{"type":"string"},"by":{"type":"string","const":"file"}},"required":["by"],"description":"`feed.json` names it, by its name or by a pattern such as `Attribut_*`."},{"type":"object","properties":{"by":{"type":"string","const":"unmapped"}},"required":["by"],"additionalProperties":false,"description":"`feed.json` does not name it, so its values are left out."},{"type":"object","additionalProperties":false,"properties":{"alias":{"type":"string"},"by":{"type":"string","const":"alias"}},"required":["by","alias"],"description":"Its name is one Merchant Center or a common shop export gives the field, such as `artikelnummer` for `id`."},{"type":"object","additionalProperties":false,"properties":{"attribute":{"$ref":"#/components/schemas/AttributeCode"},"by":{"type":"string","const":"definition"}},"required":["by","attribute"],"description":"Its name is the code, a label or a source name of an attribute in `attributes.json`."},{"type":"object","additionalProperties":false,"properties":{"beside":{"type":"string"},"by":{"type":"string","const":"regular_price"}},"required":["by","beside"],"description":"A German shop's price pair: struck through beside the shop's price, this column is the regular price."},{"type":"object","additionalProperties":false,"properties":{"beside":{"type":"string"},"by":{"type":"string","const":"reduced_price"}},"required":["by","beside"],"description":"A German shop's price pair: beside a struck-through price, the column a shop calls its price is the reduced one."},{"type":"object","additionalProperties":false,"properties":{"prefix":{"type":"string"},"columns":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"prefix"}},"required":["by","prefix","columns"],"description":"It shares its prefix with other columns, and each of them is an attribute."},{"type":"object","additionalProperties":false,"properties":{"column":{"type":"string"},"by":{"type":"string","const":"named_by"}},"required":["by","column"],"description":"It holds attribute values whose names stand in a sibling column, as WooCommerce and Shopify write them."},{"type":"object","additionalProperties":false,"properties":{"column":{"type":"string"},"by":{"type":"string","const":"names_for"}},"required":["by","column"],"description":"It holds the attribute names for the values of another column."},{"type":"object","additionalProperties":false,"properties":{"column":{"type":"string"},"by":{"type":"string","const":"names_missing"}},"required":["by","column"],"description":"Its attribute names should stand in a column the file does not have, so its values are left out."},{"type":"object","properties":{"by":{"type":"string","const":"pairs"}},"required":["by"],"additionalProperties":false,"description":"Its cells hold several attributes as name and value pairs, such as \"Farbe: Rot | Breite: 218 cm\"."},{"type":"object","properties":{"by":{"type":"string","const":"name"}},"required":["by"],"additionalProperties":false,"description":"No alias knows it, so it is an attribute under a code made from its name."},{"type":"object","properties":{"by":{"type":"string","const":"not_for_search"}},"required":["by"],"additionalProperties":false,"description":"Shipping, tax or ads data, or another column search does not use, such as `product_detail`'s section names."},{"type":"object","properties":{"by":{"type":"string","const":"empty"}},"required":["by"],"additionalProperties":false,"description":"No row fills it."}],"description":"Why a column goes where it goes."},"Proposal":{"type":"object","additionalProperties":false,"properties":{"mapping":{"description":"Left out when the import would leave its values out.","$ref":"#/components/schemas/Column"},"reason":{"$ref":"#/components/schemas/Reason"}},"required":["reason"],"description":"The import's own proposal for a column: where it would go without `feed.json`, and why."},"Feed":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"format":{"$ref":"#/components/schemas/Format","description":"How the file is read, where recognizing it from its bytes would guess wrong."},"columns":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Column"},"description":"Each column by its name as the header writes it, and where its values go. A `*` in a name stands for the rest\nof a column's name, such as `Attribut_*` for `Attribut_Farbe`; a column named exactly wins over a pattern."},"uses":{"$ref":"#/components/schemas/Uses","description":"What the export's product details are for, as a decision model judged them when the export arrived. The import\nfollows it where the release leaves `categories.json` or `types.json` out, so it never freezes the shop: a\ndetail it does not name is derived as if it were not there, and the next export's answer replaces it."}},"description":"`feed.json`: how the shop's export is read and where each of its columns goes. With this file, the mapping is\nexactly what it says: a column it does not name is reported as unmapped and never guessed, so a new column in the\nexport cannot quietly change the shop."},"Uses":{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"},"description":"Filters, the one shoppers need most first. Each is still a filter only where it passes the import's own test\non a page, half the page's products and a choice of values."},"search":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"},"description":"Details whose words find their products when shoppers type them, beside title, brand and description."},"none":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"},"description":"Details shoppers neither filter by nor search, though the import would make them a filter."}},"description":"What each product detail is for: a filter, found by search, or neither. A detail is named in one list at most."},"Defaults":{"type":"object","additionalProperties":false,"properties":{"types":{"$ref":"#/components/schemas/Types"},"channels":{"$ref":"#/components/schemas/Channels"},"attributes":{"$ref":"#/components/schemas/Attributes"},"categories":{"$ref":"#/components/schemas/Categories"},"ranking":{"$ref":"#/components/schemas/Ranking"},"rules":{"$ref":"#/components/schemas/Rules"},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Derived"},"description":"Why each derived setting is what it is, in the order of the files above."}},"description":"The release files derived from a catalog, each with why every setting in it is what it is. Only files the release\nleaves out are derived; a file left empty here was not derived."},"Types":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"types":{"type":"array","items":{"$ref":"#/components/schemas/TypeDefinition"},"description":"The types this release searches. Built-in types are `product`, `service`, `category`, `content` and `brand`;\nany other code defines a type of the merchant's own."}},"description":"`types.json`: the entity types a release searches, and how each is listed, searched and returned."},"TypeDefinition":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"label":{"$ref":"#/components/schemas/Localized_string"},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/Capability"},"description":"What the type can do. Built-in types have fixed capabilities and leave this out: `product` has `variants` and\n`offers`, `service` has `offers`, `category` has `tree`, `content` and `brand` have none."},"listing":{"$ref":"#/components/schemas/ListingGrain","description":"What one result tile represents. Default: one tile per product."},"search":{"type":"array","items":{"$ref":"#/components/schemas/FieldPath"},"description":"The searched fields in priority order: a word found in an earlier field ranks a hit higher. Default: `title`,\n`keywords`, `description`. Their order goes live as a setting; which fields are searched needs an index run."},"typos":{"description":"Whether, and from how many letters on, a word finds what holds it with a typo. Default: on, one typo from 5\nletters, two from 9. It goes live as a setting.","$ref":"#/components/schemas/TypoTolerance"},"no_typos":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"},"description":"Searched attributes whose words are found only as written or by their start, never with a typo, such as part\nnumbers: one character off names another part. Default: derived by every run, the identifier attributes the\ntype searches whose values nearly always belong to one product alone; `[]` exempts none. A change needs an\nindex run, since the engine reads every document again for it."},"result":{"type":"array","items":{"$ref":"#/components/schemas/FieldPath"},"description":"The fields a result returns. Default: `title`, `url` and `image`, and for types with offers also `price`,\n`sale_price`, `currency` and `availability`."},"details":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"},"description":"The attributes a product lookup lists as the product's and its variants' details, in order, such as\n`[\"material\", \"width\", \"height\"]`. Default: every attribute, in the order of `attributes.json`."}},"required":["type"],"description":"An entity type: its capabilities, what one result tile represents, and which fields are searched and returned."},"Localized_string":{"description":"A plain value, or a map from locale to value such as { \"de\": \"Farbe\", \"en\": \"Color\" }.","anyOf":[{"type":"string"},{"type":"object","propertyNames":{"$ref":"#/components/schemas/Locale"},"additionalProperties":{"type":"string"},"minProperties":1}]},"Capability":{"oneOf":[{"type":"string","const":"variants","description":"One level of buyable variations."},{"type":"string","const":"offers","description":"Price and availability per channel."},{"type":"string","const":"tree","description":"A place in a tree with one parent."}],"description":"A behavior a type opts into."},"ListingGrain":{"oneOf":[{"type":"string","enum":["product","variant"]},{"type":"object","properties":{"axis":{"$ref":"#/components/schemas/AttributeCode"}},"required":["axis"],"additionalProperties":false}],"description":"What one result tile represents: `\"product\"`, `\"variant\"`, or one tile per value of an axis, `{ \"axis\": \"color\" }`."},"TypoTolerance":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean","description":"Off, a word finds only what holds it as written or starts with it. Default: on.","default":true},"one_typo":{"type":"integer","format":"uint8","minimum":0,"maximum":255,"description":"The fewest letters a word needs to find with one typo. Default: 5.","default":5},"two_typos":{"type":"integer","format":"uint8","minimum":0,"maximum":255,"description":"The fewest letters a word needs to find with two typos; at least `one_typo`. Default: 9.","default":9}},"description":"How a type's words forgive typos. A typo is a letter added, left out, changed, or two neighbours swapped; one at\nthe first letter counts as two."},"Channels":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"channels":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}},"context":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"type":"array","items":{"type":"string"}}},"description":"The context keys a request may carry and the values each allows, such as\n`{ \"customer_group\": [\"b2c\", \"b2b\"] }`. A key or value not declared here is a validation error, not a rule that\nnever fires. Context never carries personal data."}},"description":"`channels.json`: the channels a merchant sells in, and the context keys requests may carry."},"Channel":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/ChannelId"},"label":{"$ref":"#/components/schemas/Localized_string"},"locales":{"type":"array","items":{"$ref":"#/components/schemas/Locale"},"description":"The locales the channel serves; the first is its default."},"currency":{"$ref":"#/components/schemas/Currency"},"countries":{"type":"array","items":{"$ref":"#/components/schemas/Country"}},"assortment":{"description":"An enforced filter on everything the channel shows, such as `{ \"not\": { \"b2b_only\": true } }`. Shoppers cannot\nremove it. A product is in the assortment when it has an offer in the channel and matches this selector.","$ref":"#/components/schemas/Selector"}},"required":["id","locales","currency"],"description":"A market or storefront with its locales, currency and assortment."},"Country":{"type":"string","pattern":"^[A-Z]{2}$","description":"An ISO 3166-1 alpha-2 country code, such as `DE`."},"Attributes":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"attributes":{"type":"array","items":{"$ref":"#/components/schemas/AttributeDefinition"}}},"description":"`attributes.json`: what each attribute value means."},"AttributeDefinition":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/AttributeCode"},"type":{"$ref":"#/components/schemas/AttributeType"},"label":{"$ref":"#/components/schemas/Localized_string"},"level":{"$ref":"#/components/schemas/Level","description":"Whether the value belongs to the product or differs per variant. Default: `product`."},"multiple":{"type":"boolean","description":"The value is a list, such as several materials."},"localized":{"type":"boolean","description":"The value differs per locale. For `text` and, rarely, `option`."},"unit":{"description":"Quantities and ranges: the one canonical unit every value is converted to.","$ref":"#/components/schemas/Unit"},"precision":{"type":"integer","format":"uint8","minimum":0,"maximum":255,"description":"Numbers, quantities and ranges: the decimal places shown."},"values":{"type":"array","items":{"$ref":"#/components/schemas/OptionValue"},"description":"Options: the known values, in display order. Their aliases merge other spellings into them; a value nobody\nlisted becomes a value of its own and a merge suggestion in the panel, so nothing is dropped."},"groups":{"type":"array","items":{"$ref":"#/components/schemas/OptionGroup"},"description":"Options: groups that show different values under one filter, such as burgundy and red under Red."},"color_groups":{"type":"boolean","description":"Options: the import places values that have no group in a color group, by the built-in color words and by\ntheir swatch. An attribute without groups of its own has the built-in ones."},"filterable":{"type":"boolean","description":"Selectors, filters and facets may use the attribute. Changing it needs an index run."},"sortable":{"type":"boolean","description":"Sort options may use the attribute. Changing it needs an index run."},"detect_in_query":{"type":"boolean","description":"The query is searched for the attribute's values, so \"grauer teppich\" filters on grey."},"source_names":{"type":"array","items":{"type":"string"},"description":"Column names the import maps onto this attribute, in any language."}},"required":["code","type"],"description":"What an attribute's values mean: their type, unit, known values and how they are used. Facets are laid out in\n`categories.json`, searched fields in `types.json`."},"AttributeType":{"oneOf":[{"type":"string","enum":["boolean"]},{"type":"string","const":"text","description":"Free text: searchable, never a filter."},{"type":"string","const":"identifier","description":"GTIN, MPN, ISBN and similar: exact match after normalization."},{"type":"string","const":"option","description":"Enumerated values with labels, aliases, order, groups and swatches."},{"type":"string","const":"number","description":"A number without a unit."},{"type":"string","const":"quantity","description":"A number with a unit of one dimension."},{"type":"string","const":"range","description":"A minimum and a maximum with a unit, such as age 3 to 6."},{"type":"string","const":"date","description":"A date or a point in time."}]},"Level":{"type":"string","enum":["product","variant"]},"OptionValue":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/ValueCode"},"label":{"$ref":"#/components/schemas/Localized_string"},"aliases":{"type":"array","items":{"type":"string"},"description":"Spellings that mean this value, matched without regard to case, such as `Rot` and `rouge` for `red`."},"slug":{"description":"Its word in URLs, per locale, such as `anthrazit`. It is made from the label when the value is created and stays\nwhen the label changes, so a URL that holds it never moves by accident. A release that leaves it out has it made\nfrom the label at every run, and the run report names it.","$ref":"#/components/schemas/Localized_UrlSlug"},"slug_aliases":{"description":"The slugs it had before, per locale: a URL that holds one is read as the value and leads to its slug. A write\nthat changes the slug keeps the old one here; deleting one is deliberate.","$ref":"#/components/schemas/Localized_Array_of_UrlSlug"},"group":{"description":"The group or groups the value is shown under.","$ref":"#/components/schemas/OneOrMany_ValueCode"},"swatch":{"$ref":"#/components/schemas/Swatch"},"image":{"type":"string","description":"A swatch image, such as a pattern or a mix of colors that no single color shows; its URL."},"description":{"description":"What the value means, shown under it in a facet and read with it, such as \"Coated fabric with the look of\nleather\".","$ref":"#/components/schemas/Localized_string"}},"required":["code"],"description":"A known value of an option attribute."},"ValueCode":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{0,63}$","description":"The code of an option value or option group, such as `grey` or `160x230`."},"Localized_UrlSlug":{"description":"A plain value, or a map from locale to value such as { \"de\": \"Farbe\", \"en\": \"Color\" }.","anyOf":[{"$ref":"#/components/schemas/UrlSlug"},{"type":"object","propertyNames":{"$ref":"#/components/schemas/Locale"},"additionalProperties":{"$ref":"#/components/schemas/UrlSlug"},"minProperties":1}]},"UrlSlug":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","description":"An option value's or group's word in a URL, such as `anthrazit` in `?farbe=anthrazit` or `farbe--anthrazit`. It never holds two dashes in a row, so a path may join a facet's name and a slug with `--`."},"Localized_Array_of_UrlSlug":{"description":"A plain value, or a map from locale to value such as { \"de\": \"Farbe\", \"en\": \"Color\" }.","anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/UrlSlug"}},{"type":"object","propertyNames":{"$ref":"#/components/schemas/Locale"},"additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/UrlSlug"}},"minProperties":1}]},"OneOrMany_ValueCode":{"description":"One value, or a list meaning any of them.","anyOf":[{"$ref":"#/components/schemas/ValueCode"},{"type":"array","items":{"$ref":"#/components/schemas/ValueCode"},"minItems":1}]},"Swatch":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","description":"A swatch color in hexadecimal notation, such as `#C62828`."},"OptionGroup":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/ValueCode"},"label":{"$ref":"#/components/schemas/Localized_string"},"aliases":{"type":"array","items":{"type":"string"},"description":"Words in a query that mean the group, such as `grau` for grey."},"slug":{"description":"Its word in URLs, per locale, such as `anthrazit`. It is made from the label when the value is created and stays\nwhen the label changes, so a URL that holds it never moves by accident. A release that leaves it out has it made\nfrom the label at every run, and the run report names it.","$ref":"#/components/schemas/Localized_UrlSlug"},"slug_aliases":{"description":"The slugs it had before, per locale: a URL that holds one is read as the value and leads to its slug. A write\nthat changes the slug keeps the old one here; deleting one is deliberate.","$ref":"#/components/schemas/Localized_Array_of_UrlSlug"},"swatch":{"$ref":"#/components/schemas/Swatch"},"image":{"type":"string","description":"A swatch image, such as the pattern that stands for \"mixed colors\"; its URL."},"description":{"description":"What the group holds, shown under it in a facet and read with it.","$ref":"#/components/schemas/Localized_string"}},"required":["code"],"description":"Values shown under one filter. A selector naming a group means every value in it; a code that is both a value and\na group means the group."},"Categories":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"default":{"$ref":"#/components/schemas/PageLayout","description":"What search pages and every category use unless a node says otherwise."},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/CategoryNode"},"description":"Settings per category node. A node inherits from its parent and overrides only what differs."}},"description":"`categories.json`: the facets and sort options of search and category pages."},"PageLayout":{"type":"object","additionalProperties":false,"properties":{"facets":{"type":"array","items":{"$ref":"#/components/schemas/Facet"},"description":"The facets in display order."},"groups":{"type":"array","items":{"$ref":"#/components/schemas/FacetGroup"},"description":"Facets drawn together under one heading."},"sort":{"$ref":"#/components/schemas/Sorting"}},"description":"The facets and sort options of a page."},"Facet":{"anyOf":[{"$ref":"#/components/schemas/FieldCode"},{"$ref":"#/components/schemas/ShownFacet"}],"description":"A facet on a page: a field code such as `\"color\"`, or an object that also says how it is shown. A field code alone\nis shown as `default` shows it, or in the field's natural kind."},"ShownFacet":{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldCode","description":"An attribute code, or one of `category`, `brand`, `price`, `on_sale`, `availability` and `delivery_days`, which\nreads the latest day of delivery."},"kind":{"description":"How the facet is shown. Default: the field's natural kind, such as a list for options and a range for numbers.","$ref":"#/components/schemas/FacetKind"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/Bucket"},"description":"The bounds of `buckets` and `relative` facets, in ascending order."},"show":{"description":"Options: show the values, or the groups they belong to.","$ref":"#/components/schemas/FacetShow"},"order":{"description":"The order of a facet's values. Default: most results first; buckets keep their own order; grades, buttons and a\nselect keep the order the values are listed in.","$ref":"#/components/schemas/FacetOrder"},"single":{"type":"boolean","description":"One value at most, chosen from a radio group with \"Any\" first, such as a delivery time whose buckets overlap."},"description":{"description":"Text shown under the facet's name and read with it, such as what a delivery time counts from. Never a tooltip.","$ref":"#/components/schemas/Localized_string"},"headings":{"type":"array","items":{"$ref":"#/components/schemas/FacetHeading2"},"description":"Parts of the list under headings of their own, such as \"Wood\" above oak and walnut, in order. Values no heading\nnames follow the last one."},"search":{"type":"boolean","description":"A search box above the values, for long lists such as brands."},"limit":{"type":"integer","format":"uint32","minimum":1,"description":"The most values a list or swatch carries with the page before `more` says there are others: the most frequent\nones, and every chosen one. Default: the limit `facet_values`. A select or a scale that shows every value raises\nit."},"display":{"description":"How the values look beyond their kind. Default: the kind's own look; a rating shows stars.","$ref":"#/components/schemas/FacetDisplay"},"custom_display":{"description":"A storefront display of the shop's own, by the name the shop registered it under, such as `color_tiles`. A\nstorefront without it draws `display`, or the kind's own look, so `display` keeps what it means for the data.","$ref":"#/components/schemas/DisplayCode"}},"required":["field"],"description":"A facet and how it is shown."},"Bucket":{"type":"object","additionalProperties":false,"properties":{"from":{"type":"number"},"to":{"type":"number"},"label":{"$ref":"#/components/schemas/Localized_string"}},"description":"A bucket from `from` (inclusive) to `to` (exclusive); either end may be open. Without `from`, a bucket counts\neverything up to its `to`, as in \"up to 2 weeks\"; without `to`, everything from its `from` on, as in \"4 stars and\nmore\". Such open buckets may overlap."},"FacetShow":{"type":"string","enum":["values","groups"]},"FacetOrder":{"oneOf":[{"type":"string","const":"count","description":"Most results first."},{"type":"string","const":"value_order","description":"The order of the attribute's values."},{"type":"string","const":"label","description":"Alphabetical by label."}]},"FacetHeading2":{"type":"object","additionalProperties":false,"properties":{"label":{"$ref":"#/components/schemas/Localized_string"},"values":{"type":"array","items":{"$ref":"#/components/schemas/ValueCode"},"description":"Value codes, or group codes where the facet shows groups, in display order."}},"required":["label","values"],"description":"A heading inside a facet and the values it stands above."},"FacetGroup":{"type":"object","additionalProperties":false,"properties":{"group":{"$ref":"#/components/schemas/GroupCode","description":"Names the group in the response and the trace."},"label":{"$ref":"#/components/schemas/Localized_string","description":"The heading."},"facets":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"},"description":"The fields of its facets, at least two, each in one group at most.","minItems":2},"display":{"description":"How the group is drawn. Default: its facets one under the other beneath the heading.","$ref":"#/components/schemas/GroupDisplay"}},"required":["group","label","facets"],"description":"Several of a page's facets under one heading, such as a tyre's width, ratio and rim as \"Tyre size\". The facets keep\ntheir own settings in `facets`; a group only says which belong together. It stands where the first of them stands\non the page and draws them in its own order, and a page that lists fewer than two of them draws them apart. Each\nfacet is counted under the choices of the others, as every facet is, and the page counts the group whole or not at\nall."},"Sorting":{"type":"object","additionalProperties":false,"properties":{"default":{"description":"The order a page starts with, where the shopper chose none and the request has no words, which rank by\nrelevance. The nearest node up the tree that sets one decides; the layout's `default` is the last. Default:\n`relevance`.","$ref":"#/components/schemas/SortCode"},"options":{"type":"array","items":{"$ref":"#/components/schemas/SortCode"},"description":"The orders a shopper can choose, in display order. A page offers those of the nearest node that lists some, or\nthe layout's `default`."}},"description":"The sort options a page offers. `relevance` is built in; the others are defined in `ranking.json`."},"CategoryNode":{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"facets":{"type":"array","items":{"$ref":"#/components/schemas/Facet"},"description":"The facets in display order; replaces the inherited list."},"groups":{"type":"array","items":{"$ref":"#/components/schemas/FacetGroup"},"description":"Facets drawn together under one heading; replaces the inherited groups, and an empty list leaves the page\nwithout any."},"sort":{"$ref":"#/components/schemas/Sorting"},"where":{"description":"A smart category: entities matching this selector belong to the node, in addition to those assigned in the\ncatalog. \"Sale\" is `{ \"on_sale\": true }`.","$ref":"#/components/schemas/Selector"},"filter_pages":{"type":"array","items":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"}},"description":"The filter pages of the node and the categories below it, from the combinations `routes.json` lists:\n`[[\"gender\"]]` narrows them, `[]` leaves none. Default: the parent's."}},"required":["category"],"description":"One category node's settings."},"Ranking":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"signals":{"type":"array","items":{"$ref":"#/components/schemas/SignalDefinition"},"description":"How each signal is read before it is weighted."},"weights":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"type":"number"}}}},"description":"Per type, the weight of each signal in the business score. A weight change needs an index run."},"strengths":{"$ref":"#/components/schemas/Strengths","description":"The multipliers behind the strengths `slight`, `medium` and `strong`."},"sorts":{"type":"array","items":{"$ref":"#/components/schemas/SortDefinition"},"description":"The sort options pages may offer besides `relevance`."},"sections":{"type":"array","items":{"$ref":"#/components/schemas/TypeCode"},"description":"The order of sections in mixed results. Types not listed follow in the order of `types.json`."}},"description":"`ranking.json`: signals and weights, boost and bury strengths, sort options and section order."},"SignalDefinition":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/SignalCode"},"direction":{"$ref":"#/components/schemas/Direction"},"normalize":{"$ref":"#/components/schemas/Normalization"}},"required":["code","direction","normalize"],"description":"A signal and how it is normalized into the business score."},"Direction":{"oneOf":[{"type":"string","enum":["higher_is_better","lower_is_better"]},{"type":"string","const":"newer_is_better","description":"For dates: the more recent, the better."}]},"Normalization":{"oneOf":[{"type":"string","const":"percentile","description":"Its percentile among all entities of the type, taken at each full index run."},{"type":"object","properties":{"damped":{"$ref":"#/components/schemas/Damping"}},"required":["damped"],"additionalProperties":false,"description":"An average pulled towards the mean while few votes back it, such as a rating with few reviews."},{"type":"object","properties":{"half_life_days":{"type":"integer","format":"uint32","minimum":0}},"required":["half_life_days"],"additionalProperties":false,"description":"Halves its effect every this many days; for dates."}],"description":"How a signal becomes a number between 0 and 1."},"Damping":{"type":"object","additionalProperties":false,"properties":{"count_signal":{"$ref":"#/components/schemas/SignalCode","description":"The signal that counts the votes, such as `rating_count`. It is read from the catalog and needs no definition\nof its own."},"prior_count":{"type":"integer","format":"uint32","minimum":0,"description":"How many votes count as much as the mean."}},"required":["count_signal","prior_count"]},"Strengths":{"type":"object","additionalProperties":false,"properties":{"boost":{"$ref":"#/components/schemas/StrengthScale"},"bury":{"$ref":"#/components/schemas/StrengthScale"}},"required":["boost","bury"],"description":"The multipliers of each strength. Boosts above 1, buries below."},"StrengthScale":{"type":"object","additionalProperties":false,"properties":{"slight":{"type":"number"},"medium":{"type":"number"},"strong":{"type":"number"}},"required":["slight","medium","strong"]},"SortDefinition":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/SortCode"},"label":{"$ref":"#/components/schemas/Localized_string"},"by":{"$ref":"#/components/schemas/FieldPath","description":"`price`, `delivery_days`, `discount` (the reduction a sale gives, in percent of the regular price), a sortable\n`attributes.<code>`, or `signals.<code>` of a defined signal."},"direction":{"$ref":"#/components/schemas/SortDirection"}},"required":["code","by","direction"],"description":"A sort option, such as `price_asc` over `price`."},"Rules":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"rules":{"type":"array","items":{"$ref":"#/components/schemas/Rule"},"description":"The rules in precedence order: the first rule is the highest."}},"description":"`rules.json`: the rules in precedence order."},"Rule":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string","description":"One line, up to 200 characters, shown in the panel, the trace and diffs."},"enabled":{"type":"boolean","description":"A disabled rule is not evaluated. Default: `true`."},"when":{"description":"The condition over the request. Without one, the rule always matches.","$ref":"#/components/schemas/Condition"},"then":{"$ref":"#/components/schemas/Effects","description":"What the rule does: at least one effect."},"schedule":{"description":"When the rule is active, checked against the time in the request, never the engine's clock.","$ref":"#/components/schemas/TimeWindow"},"origin":{"description":"Where the rule came from. Without it, the merchant made it.","$ref":"#/components/schemas/Origin"}},"required":["id","description","then"]},"Condition":{"type":"object","additionalProperties":false,"properties":{"query":{"description":"The query as typed, after normalization. Matching is literal: no typos, synonyms or plurals.","$ref":"#/components/schemas/QueryMatch"},"on":{"description":"The surface the request comes from.","$ref":"#/components/schemas/OneOrMany_Surface"},"category":{"description":"The category page being browsed.","$ref":"#/components/schemas/CategoryMatch"},"resolved":{"description":"What the query named. Rules that resolve or rewrite run before resolution and cannot read it.","$ref":"#/components/schemas/ResolvedCondition"},"channel":{"$ref":"#/components/schemas/OneOrMany_ChannelId"},"locale":{"$ref":"#/components/schemas/OneOrMany_Locale"},"context":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"$ref":"#/components/schemas/OneOrMany_string"}},"description":"Declared context keys and the values they must have, such as `{ \"customer_group\": \"b2b\" }`."},"any":{"type":"array","items":{"$ref":"#/components/schemas/Condition"},"minItems":1},"all":{"type":"array","items":{"$ref":"#/components/schemas/Condition"},"minItems":1},"not":{"$ref":"#/components/schemas/Condition"}},"description":"A predicate over the request (`when`). Without a condition, a rule always matches.\n\n`{ \"on\": \"search\", \"query\": { \"contains\": [\"bremsen\", \"bremse\"] } }`"},"QueryMatch":{"oneOf":[{"type":"object","properties":{"is":{"$ref":"#/components/schemas/OneOrMany_string"}},"required":["is"],"additionalProperties":false,"description":"The query is exactly this phrase."},{"type":"object","properties":{"contains":{"$ref":"#/components/schemas/OneOrMany_string"}},"required":["contains"],"additionalProperties":false,"description":"The query contains this phrase as a contiguous run of words."},{"type":"object","properties":{"starts_with":{"$ref":"#/components/schemas/OneOrMany_string"}},"required":["starts_with"],"additionalProperties":false,"description":"The query begins with this phrase."},{"type":"object","properties":{"empty":{"type":"boolean"}},"required":["empty"],"additionalProperties":false,"description":"True: nothing was typed. False: something was."}],"description":"How the query must look. A list of phrases means any of them."},"OneOrMany_Surface":{"description":"One value, or a list meaning any of them.","anyOf":[{"$ref":"#/components/schemas/Surface"},{"type":"array","items":{"$ref":"#/components/schemas/Surface"},"minItems":1}]},"ResolvedCondition":{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryMatch"},"filters":{"description":"The detected filters, as a selector over the detected values.","$ref":"#/components/schemas/Selector"},"complete":{"type":"boolean","description":"True when the query resolved completely and no text is left."}},"description":"What the query named: its category, its detected filters, and whether any text is left."},"OneOrMany_ChannelId":{"description":"One value, or a list meaning any of them.","anyOf":[{"$ref":"#/components/schemas/ChannelId"},{"type":"array","items":{"$ref":"#/components/schemas/ChannelId"},"minItems":1}]},"OneOrMany_Locale":{"description":"One value, or a list meaning any of them.","anyOf":[{"$ref":"#/components/schemas/Locale"},{"type":"array","items":{"$ref":"#/components/schemas/Locale"},"minItems":1}]},"Effects":{"type":"object","additionalProperties":false,"properties":{"resolve":{"type":"array","items":{"$ref":"#/components/schemas/Decision"},"description":"Decides what an ambiguous term means. Per term, the higher rule decides."},"rewrite":{"description":"Removes words from the query before it is resolved and searched. Removals of all rules are united.","$ref":"#/components/schemas/Rewrite"},"redirect":{"description":"Sends the shopper elsewhere. The highest redirect wins and ends planning.","$ref":"#/components/schemas/Redirect"},"filter":{"description":"Narrows the results. Shoppers see it as a chip they can remove.","$ref":"#/components/schemas/Filter"},"hide":{"type":"array","items":{"$ref":"#/components/schemas/Hide"},"description":"Removes entities from the results. Hide beats pin."},"pin":{"type":"array","items":{"$ref":"#/components/schemas/Pin"},"description":"Puts entities at fixed positions, within every filter."},"boost":{"type":"array","items":{"$ref":"#/components/schemas/Shift"},"description":"Moves entities up, within relevance."},"bury":{"type":"array","items":{"$ref":"#/components/schemas/Shift"},"description":"Moves entities down, within relevance."},"sections":{"type":"array","items":{"$ref":"#/components/schemas/TypeCode"},"description":"The order of sections. Types not listed follow in the release's order."},"place":{"type":"array","items":{"$ref":"#/components/schemas/BannerPlacement"},"description":"Puts banners and teasers above, among or below the results. A placement is not a hit: it changes no total,\nfacet, page size or order of hits."}},"description":"What a rule does. Understand-phase effects (`resolve`, `rewrite`) run before the query is resolved; `redirect`\nends planning; the rest shape the results, and `place` puts banners beside them."},"Decision":{"type":"object","additionalProperties":false,"properties":{"term":{"type":"string"},"as":{"$ref":"#/components/schemas/Meaning"}},"required":["term","as"],"description":"What one term means: values such as `{ \"term\": \"puma\", \"as\": { \"brand\": \"puma\" } }`, a category such as\n`\"as\": { \"category\": \"parts/brakes\" }`, or `\"as\": \"text\"` to keep it as text."},"Meaning":{"anyOf":[{"$ref":"#/components/schemas/KeepAsText"},{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"$ref":"#/components/schemas/Scalar"}}}]},"KeepAsText":{"type":"string","enum":["text"]},"Rewrite":{"type":"object","additionalProperties":false,"properties":{"remove":{"type":"array","items":{"type":"string"}}},"required":["remove"]},"Redirect":{"anyOf":[{"$ref":"#/components/schemas/EntityRedirect"},{"$ref":"#/components/schemas/UrlRedirect"}],"description":"Where a redirect goes: an entity's page, whose URL follows the merchant's catalog, or a fixed URL."},"EntityRedirect":{"type":"object","additionalProperties":false,"properties":{"to":{"$ref":"#/components/schemas/EntityRef"},"filters":{"$ref":"#/components/schemas/Selector","description":"With a category: the filters chosen on its page, as a request's filters name them."}},"required":["to"],"description":"An entity's own page: `{ \"to\": \"service:svc-brake-pads-front\" }`, or a category's with filters chosen,\n`{ \"to\": \"category:women/shoes\", \"filters\": { \"color\": \"red\" } }`. If the entity has left the catalog, the shopper\ngets the normal search and the trace says why."},"UrlRedirect":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string"}},"required":["url"],"description":"A fixed URL: a path such as `/werkstatt`, or one that starts with `https://`."},"Filter":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"where":{"$ref":"#/components/schemas/Selector"}},"required":["where"],"description":"A filter a rule adds, optionally only for one type's section."},"Hide":{"type":"object","additionalProperties":false,"properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"entities":{"type":"array","items":{"$ref":"#/components/schemas/EntityRef"}},"where":{"$ref":"#/components/schemas/Selector"},"type":{"description":"With `where`: only entities of this type.","$ref":"#/components/schemas/TypeCode"}},"description":"The entities an effect is about: one entity, several, or all that match a selector, optionally of one type."},"Pin":{"type":"object","additionalProperties":false,"properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"position":{"type":"integer","format":"uint32","minimum":0,"description":"The slot, counted from 1. Positions are unique within a rule; on a tie between rules the higher one takes the\nslot and the other moves to the next free one."},"sponsored":{"description":"A paid placement: the hit names its campaign, so a storefront marks it as an ad and reports its views and clicks\nfor the campaign. It is placed as any pin is, only where the product meets the page's category and filters.","$ref":"#/components/schemas/Sponsored"}},"required":["entity","position"]},"Shift":{"type":"object","additionalProperties":false,"properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"entities":{"type":"array","items":{"$ref":"#/components/schemas/EntityRef"}},"where":{"$ref":"#/components/schemas/Selector"},"type":{"description":"With `where`: only entities of this type.","$ref":"#/components/schemas/TypeCode"},"strength":{"$ref":"#/components/schemas/Strength"}},"description":"A boost or bury.","required":["strength"]},"BannerPlacement":{"type":"object","additionalProperties":false,"properties":{"slot":{"$ref":"#/components/schemas/Slot"},"position":{"type":"integer","format":"uint32","minimum":1,"description":"With `grid`, and only there: the tile it sits before, counted from 1 across the pages of the listed section,\nwhose tiles are the page's products, or its services on a workshop's page. Positions are unique within a rule;\non a tie between rules the higher one takes the position and the other moves to the next free one, as pins do.\nA position past the last tile the pages reach shows nowhere, and the trace says why."},"banner":{"$ref":"#/components/schemas/Banner"}},"required":["slot","banner"],"description":"A banner at a place of the page: `{ \"slot\": \"top\", \"banner\": { ... } }`, or among the tiles,\n`{ \"slot\": \"grid\", \"position\": 5, \"banner\": { ... } }`."},"Banner":{"type":"object","additionalProperties":false,"properties":{"kind":{"$ref":"#/components/schemas/BannerKind"},"title":{"$ref":"#/components/schemas/Localized_string","description":"The headline, up to 200 characters."},"text":{"description":"A line or two under the title.","$ref":"#/components/schemas/Localized_string"},"image":{"$ref":"#/components/schemas/BannerImage"},"link":{"description":"Where it leads, written as a redirect's target: an entity's page, such as `{ \"to\": \"category:wohnen/sofas\" }`\nor `{ \"to\": \"content:sofa-ratgeber\" }`, or a fixed URL, `{ \"url\": \"/sale\" }`. Without one it only informs.","$ref":"#/components/schemas/Redirect"}},"required":["kind","title"],"description":"What a placement shows: a campaign's banner or a teaser to a page, with its words in every locale the release\nserves."},"BannerImage":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","description":"A path such as `/media/summer.jpg`, or a URL that starts with `https://`."},"alt":{"$ref":"#/components/schemas/Localized_string","description":"What the image shows, for a shopper who cannot see it."}},"required":["url","alt"],"description":"A banner's image and the words that stand for it."},"TimeWindow":{"type":"object","additionalProperties":false,"properties":{"from":{"$ref":"#/components/schemas/Timestamp"},"until":{"$ref":"#/components/schemas/Timestamp"}},"description":"A window in time: from `from` (inclusive) until `until` (exclusive). Either end may be open."},"Derived":{"type":"object","additionalProperties":false,"properties":{"file":{"type":"string","description":"The release file the setting is written in, such as `attributes.json`."},"setting":{"type":"string","description":"The setting within it, as a person names it: an attribute's code such as `width`, a channel's id, a type's\ncode, a sort option's or signal's code, `weights.<type>.<signal>`, `default.sort`, a rule's id, a facet as\n`default.facets.<field>` or `<category id>.facets.<field>`, and an identifier a type searches as\n`<type>.no_typos.<attribute>`."},"reason":{"$ref":"#/components/schemas/DerivedBy"}},"required":["file","setting","reason"],"description":"One derived setting and what in the catalog decided it."},"DerivedBy":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"count":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"entities"}},"required":["by","count"],"description":"The catalog holds entities of this built-in type, or the export makes them, such as categories from paths."},{"type":"object","additionalProperties":false,"properties":{"locale":{"$ref":"#/components/schemas/Locale"},"share":{"type":"integer","format":"uint8","minimum":0,"maximum":255},"by":{"type":"string","const":"language"}},"required":["by","locale","share"],"description":"Most words that tell languages apart are this language's, in this share of them, in percent."},{"type":"object","additionalProperties":false,"properties":{"locale":{"$ref":"#/components/schemas/Locale"},"entities":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"keyed"}},"required":["by","locale","entities"],"description":"Texts are keyed by this locale in this many entities."},{"type":"object","additionalProperties":false,"properties":{"currency":{"$ref":"#/components/schemas/Currency"},"written":{"type":"string"},"count":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"currency"}},"required":["by","currency","written","count"],"description":"Prices name this currency, written as in `written`, in this many values."},{"type":"object","additionalProperties":false,"properties":{"currency":{"$ref":"#/components/schemas/Currency"},"locale":{"$ref":"#/components/schemas/Locale"},"by":{"type":"string","const":"language_currency"}},"required":["by","currency","locale"],"description":"No price names a currency, so the one the language usually pays in is taken."},{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/AttributeType"},"unit":{"$ref":"#/components/schemas/Unit"},"share":{"type":"integer","format":"uint8","minimum":0,"maximum":255},"values":{"type":"integer","format":"uint64","minimum":0,"description":"Values read, and how many of them differ, up to a cap."},"distinct":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"values"}},"required":["by","type","share","values","distinct"],"description":"This share of the values, in percent, reads as this type; a quantity also names the unit most of them use."},{"type":"object","additionalProperties":false,"properties":{"products":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"varies"}},"required":["by","products"],"description":"The variants carry the value in this many products, so it is the variants': an export puts there what differs\ninside a group, JSON Lines what the merchant wrote on the variants."},{"type":"object","additionalProperties":false,"properties":{"count":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"lists"}},"required":["by","count"],"description":"This many values arrive as lists, which only an attribute that takes several values reads."},{"type":"object","additionalProperties":false,"properties":{"share":{"type":"integer","format":"uint8","minimum":0,"maximum":255},"by":{"type":"string","const":"colors"}},"required":["by","share"],"description":"This share of the values, in percent, are color words, so the import places them in color groups."},{"type":"object","additionalProperties":false,"properties":{"share":{"type":"integer","format":"uint8","minimum":0,"maximum":255},"by":{"type":"string","const":"codes"}},"required":["by","share"],"description":"This share of the values, in percent, are machine codes such as `HOME_FURNITURE_AND_DECOR`: data for rules,\nnot words a shopper filters by, so the attribute is no filter."},{"type":"object","properties":{"by":{"type":"string","const":"faceted"}},"required":["by"],"additionalProperties":false,"description":"The search pages filter by this option, so a query that names one of its values or groups is read as that\nfilter (`detect_in_query`), within the token dimensions its facet already spends."},{"type":"object","additionalProperties":false,"properties":{"products":{"type":"integer","format":"uint64","minimum":0},"of":{"type":"integer","format":"uint64","minimum":0},"values":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"carried"}},"required":["by","products","of","values"],"description":"A facet: this many of the page's products carry the field, with this many different values."},{"type":"object","properties":{"by":{"type":"string","const":"always"}},"required":["by"],"additionalProperties":false,"description":"Every shop offers it: relevance and price sorting, the price and category facets."},{"type":"object","additionalProperties":false,"properties":{"count":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"offers"}},"required":["by","count"],"description":"Offers carry what it reads, such as delivery days or sale prices, in this many items."},{"type":"object","additionalProperties":false,"properties":{"signal":{"$ref":"#/components/schemas/SignalCode"},"entities":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"signal"}},"required":["by","signal","entities"],"description":"The catalog carries this signal in this many entities."},{"type":"object","additionalProperties":false,"properties":{"share":{"type":"integer","format":"uint8","minimum":0,"maximum":255},"values":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"unique"}},"required":["by","share","values"],"description":"This share of an identifier's values, in percent, of this many, belong to one product alone: a typo in one names\nanother product, so the attribute is exempt from typos."},{"type":"object","additionalProperties":false,"properties":{"share":{"type":"integer","format":"uint8","minimum":0,"maximum":255},"values":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"shared"}},"required":["by","share","values"],"description":"Only this share of an identifier's values, in percent, of this many, belong to one product alone: products share\nthem, as cross-reference numbers are, so typos still find them."},{"type":"object","additionalProperties":false,"properties":{"values":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"few"}},"required":["by","values"],"description":"Too few of an identifier's values, this many, tell whether they belong to one product each, so typos still find\nthem."}],"description":"What in the catalog decided a derived setting. Codes are stable, so the panel translates them."},"Entity":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode","description":"The entity's type, such as `product`."},"id":{"$ref":"#/components/schemas/EntityId","description":"The merchant's id, unique per type."},"title":{"description":"The name shoppers see. A plain string is in the source's locale.","$ref":"#/components/schemas/Localized_string"},"url":{"description":"The merchant's own URL for the entity. OrbSearch never generates one when the merchant supplies it.","$ref":"#/components/schemas/Localized_string"},"description":{"$ref":"#/components/schemas/Localized_string"},"image":{"type":"string","description":"The main image's URL."},"images":{"type":"array","items":{"type":"string"},"description":"Further image URLs."},"keywords":{"description":"Extra search terms from the merchant.","$ref":"#/components/schemas/Localized_Array_of_string"},"categories":{"type":"array","items":{"$ref":"#/components/schemas/CategoryAssignment"},"description":"The categories the entity sits in. The first is primary for breadcrumbs unless one is marked `primary`."},"attributes":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":true},"description":"The merchant's data, as the source sends it; the attribute definitions say what each value means."},"relations":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"type":"array","items":{"$ref":"#/components/schemas/RelationLink"}}},"description":"Links to other entities, interpreted by the relation definitions."},"signals":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"$ref":"#/components/schemas/Scalar"}},"description":"Numbers and dates for ranking that shoppers never see, such as `sales_30d` or `released_at`."},"brand":{"type":"string","description":"Products: the brand's name or id; the `brand` relation resolves it to a brand entity."},"varies_by":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"},"description":"Products: the attributes the variants differ by, such as `[\"color\", \"size\"]`."},"variants":{"type":"array","items":{"$ref":"#/components/schemas/Variant"},"description":"Products: the buyable variations, one level deep. A product without variants has one implicit variant with the\nproduct's id."},"offers":{"type":"array","items":{"$ref":"#/components/schemas/Offer"},"description":"Types with offers but without variants, such as services: price and availability per channel."},"price":{"type":"number"},"sale_price":{"type":"number","description":"The reduced price, valid within `sale_window` if one is given."},"sale_window":{"$ref":"#/components/schemas/TimeWindow"},"currency":{"$ref":"#/components/schemas/Currency"},"availability":{"$ref":"#/components/schemas/Availability"},"stock":{"type":"integer","format":"uint32","minimum":0},"delivery_days":{"$ref":"#/components/schemas/DeliveryDays"},"parent":{"description":"Categories: the parent node; a node without a parent is a root.","$ref":"#/components/schemas/CategoryId"},"path":{"description":"Categories: the merchant's URL path per locale.","$ref":"#/components/schemas/Localized_string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/ChannelId"},"description":"Categories: the channels the node exists in. Without it, the node exists in every channel."},"body":{"description":"Content: the full text.","$ref":"#/components/schemas/Localized_string"},"kind":{"type":"string","description":"Content: what kind of content it is, such as `guide` or `banner`."},"logo":{"type":"string","description":"Brands: the logo's URL."},"aliases":{"type":"array","items":{"type":"string"},"description":"Brands: other spellings of the brand's name, such as `Robert Bosch GmbH`."}},"required":["type","id"],"description":"Anything that can be found: a product, a category, a guide, a brand or an entity of the merchant's own type. Every\ntype shares this shape; a type's capabilities decide which of the type-specific fields it may use."},"Localized_Array_of_string":{"description":"A plain value, or a map from locale to value such as { \"de\": \"Farbe\", \"en\": \"Color\" }.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","propertyNames":{"$ref":"#/components/schemas/Locale"},"additionalProperties":{"type":"array","items":{"type":"string"}},"minProperties":1}]},"CategoryAssignment":{"anyOf":[{"$ref":"#/components/schemas/CategoryId"},{"$ref":"#/components/schemas/PlacedCategory"}],"description":"A category the entity sits in: its id, or an object with the id, the position in that category and whether it is\nthe primary category."},"PlacedCategory":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/CategoryId"},"position":{"type":"integer","format":"uint32","minimum":0,"description":"The starting order within the category; rules override it."},"primary":{"type":"boolean"}},"required":["id"]},"RelationLink":{"anyOf":[{"$ref":"#/components/schemas/EntityId"},{"$ref":"#/components/schemas/QualifiedLink"}],"description":"A link to another entity: its id, or an object with the id and qualifiers that belong to this link, such as \"only\nwith sport suspension\". Qualifiers are shown and traced, never filtered silently."},"QualifiedLink":{"type":"object","additionalProperties":false,"properties":{"to":{"$ref":"#/components/schemas/EntityId"},"qualifiers":{"type":"object","additionalProperties":true}},"required":["to"]},"Variant":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/EntityId"},"title":{"$ref":"#/components/schemas/Localized_string"},"url":{"$ref":"#/components/schemas/Localized_string"},"image":{"type":"string"},"images":{"type":"array","items":{"type":"string"}},"attributes":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":true}},"offers":{"type":"array","items":{"$ref":"#/components/schemas/Offer"},"description":"Price and availability per channel."},"price":{"type":"number"},"sale_price":{"type":"number","description":"The reduced price, valid within `sale_window` if one is given."},"sale_window":{"$ref":"#/components/schemas/TimeWindow"},"currency":{"$ref":"#/components/schemas/Currency"},"availability":{"$ref":"#/components/schemas/Availability"},"stock":{"type":"integer","format":"uint32","minimum":0},"delivery_days":{"$ref":"#/components/schemas/DeliveryDays"}},"required":["id"],"description":"A buyable variation of a product. Its axis values, the attributes named in the product's `varies_by`, are unique\nwithin the product."},"Offer":{"type":"object","additionalProperties":false,"properties":{"channel":{"$ref":"#/components/schemas/ChannelId"},"price":{"type":"number"},"sale_price":{"type":"number","description":"The reduced price, valid within `sale_window` if one is given."},"sale_window":{"$ref":"#/components/schemas/TimeWindow"},"currency":{"$ref":"#/components/schemas/Currency"},"availability":{"$ref":"#/components/schemas/Availability"},"stock":{"type":"integer","format":"uint32","minimum":0},"delivery_days":{"$ref":"#/components/schemas/DeliveryDays"}},"required":["channel"],"description":"Price and availability of a variant, or of an entity without variants, in one channel."},"InspectedDetail":{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldCode","description":"An attribute's code, or one of the fields every shop has: `category`, `brand`, `price`, `availability`,\n`delivery_days`, `on_sale` and `rating`."},"label":{"type":"string","description":"Its name in the shop's first language, as shoppers read it: an attribute's label, a field every shop has by\nthe built-in words such as \"Preis\". The panel names the fields every shop has in the merchant's own language."},"column":{"type":"string","description":"The column it comes from, as the header writes it."},"products":{"type":"integer","format":"uint64","minimum":0,"description":"Products that carry it, on themselves or on a variant."},"values":{"$ref":"#/components/schemas/AttributeValues"},"use":{"$ref":"#/components/schemas/DetailUse","description":"What it is for now."},"choices":{"type":"array","items":{"$ref":"#/components/schemas/DetailUse"},"description":"What it can be for: the category is always a filter, the other fields every shop has are never searched, and\ntext, codes and dates make no filter."},"facet":{"description":"The facet `categories.json` lists for it as a filter, in the shape the import gives it, such as the price's\nbuckets or the color groups as swatches. Left out when it can be no filter.","$ref":"#/components/schemas/Facet"},"look":{"description":"How a shop's filter column draws it as a filter; left out when it can be no filter.","$ref":"#/components/schemas/Look"},"unchosen":{"description":"Why the import would not make it a filter of search pages by itself, though it can be one.","$ref":"#/components/schemas/Unchosen"},"categories":{"type":"integer","format":"uint64","minimum":0,"description":"A filter only of this many category pages, where most products carry it, and not of search pages."},"buckets":{"type":"array","items":{"type":"string"},"description":"How shoppers read the filter's ranges in the shop's first language, in the facet's order, such as \"bis 10 €\";\nempty where the filter lists values."},"suggested":{"description":"What the decision model judged it is for, as `feed.json`'s `uses` holds it.","$ref":"#/components/schemas/DetailUse"}},"required":["field","products","use","choices"],"description":"A product detail as shoppers meet it: what it is for, what else it could be, and why the import chose as it did."},"AttributeValues":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"values":{"type":"array","items":{"$ref":"#/components/schemas/FacetValue"}},"ungrouped":{"type":"array","items":{"$ref":"#/components/schemas/FacetValue"}},"as":{"type":"string","const":"options"}},"required":["as","values"],"description":"Values a shopper ticks, the most common first, each with how many products carry it. An attribute with color\ngroups shows its groups with their swatches; values no group took follow in `ungrouped`."},{"type":"object","additionalProperties":false,"properties":{"min":{"type":"number"},"max":{"type":"number"},"unit":{"$ref":"#/components/schemas/Unit"},"as":{"type":"string","const":"range"}},"required":["as","min","max"],"description":"Numbers, quantities or ranges a shopper narrows: the lowest and highest, in the attribute's unit."},{"type":"object","additionalProperties":false,"properties":{"yes":{"type":"integer","format":"uint64","minimum":0},"no":{"type":"integer","format":"uint64","minimum":0},"as":{"type":"string","const":"toggle"}},"required":["as","yes","no"],"description":"Yes or no."},{"type":"object","additionalProperties":false,"properties":{"samples":{"type":"array","items":{"type":"string"}},"defined":{"type":"boolean","description":"attributes.json defines it."},"as":{"type":"string","const":"text"}},"required":["as","samples"],"description":"Text, dates and identifiers, which nobody ticks: its first values as written. An attribute attributes.json does\nnot define is kept as written and shows here."}],"description":"An attribute's values, read as its definition says."},"DetailUse":{"oneOf":[{"type":"string","const":"filter","description":"Shoppers narrow the products by its values."},{"type":"string","const":"search","description":"Its words find the products when shoppers type them."},{"type":"string","const":"none","description":"Neither."}],"description":"What a product detail is for."},"Look":{"oneOf":[{"type":"string","const":"tree","description":"The category tree."},{"type":"string","const":"list","description":"Values to tick."},{"type":"string","const":"swatches","description":"Colors as swatches."},{"type":"string","const":"tiles","description":"Sizes as tiles."},{"type":"string","const":"range","description":"A range of numbers or its buckets."},{"type":"string","const":"toggle","description":"Yes or no."},{"type":"string","const":"stars","description":"Stars from a rating."}],"description":"How a filter looks in a shop's filter column."},"Unchosen":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"products":{"type":"integer","format":"uint64","minimum":0},"of":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"rare"}},"required":["by","products","of"],"description":"Fewer than half the products carry it, so shoppers who use it hide the rest."},{"type":"object","additionalProperties":false,"properties":{"values":{"type":"integer","format":"uint64","minimum":0},"by":{"type":"string","const":"values"}},"required":["by","values"],"description":"More values than a filter lists, about fifty."},{"type":"object","properties":{"by":{"type":"string","const":"single"}},"required":["by"],"additionalProperties":false,"description":"One value only, so there is nothing to choose."},{"type":"object","properties":{"by":{"type":"string","const":"count"}},"required":["by"],"additionalProperties":false,"description":"A number without a unit that is no short list of whole values, such as a sales count or a score."},{"type":"object","properties":{"by":{"type":"string","const":"variant_number"}},"required":["by"],"additionalProperties":false,"description":"A number that differs between variants, which only buckets count exactly."},{"type":"object","properties":{"by":{"type":"string","const":"suggested"}},"required":["by"],"additionalProperties":false,"description":"The decision model judged it no filter, though the import's own test would make it one."}],"description":"Why the import would not make a detail a filter of search pages by itself. A merchant may still\nchoose it; the reason says what shoppers who use it meet."},"RejectionGroup":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/RejectionCode"},"column":{"type":"string","description":"The export's column, when the cause sits in one."},"count":{"type":"integer","format":"uint64","minimum":0,"description":"How many rows, or values, it left out."},"examples":{"type":"array","items":{"$ref":"#/components/schemas/Rejection"},"description":"The first of them, up to `LISTED_EXAMPLES`."}},"required":["code","count","examples"],"description":"Rejections with one cause: the same code, and in a mapped export the same column."},"RejectionCode":{"oneOf":[{"type":"string","const":"unreadable_feed","description":"The file cannot be read as the kind it seems to be, such as an XML feed that breaks off; nothing after the\nplace it names was read."},{"type":"string","const":"not_utf8","description":"The row is not UTF-8 text."},{"type":"string","const":"not_json","description":"The row is not valid JSON."},{"type":"string","const":"not_an_entity","description":"The row is JSON, but not one entity as an object."},{"type":"string","const":"unknown_field","description":"The row has a field no entity, variant or offer has."},{"type":"string","const":"invalid_field","description":"A field's value has the wrong shape, such as a price written as text."},{"type":"string","const":"unknown_type","description":"The row's type is not a type of the release."},{"type":"string","const":"field_not_of_type","description":"The row sets a field its type does not have, such as variants on a category."},{"type":"string","const":"misplaced_offer","description":"Offer fields sit on a product that has variants, or both on an entity and in its offers."},{"type":"string","const":"unknown_channel","description":"An offer names a channel the release does not have, or offer fields need a channel to be named."},{"type":"string","const":"wrong_currency","description":"An offer's currency is not its channel's."},{"type":"string","const":"duplicate_offer","description":"Two offers of one entity or variant name the same channel."},{"type":"string","const":"duplicate_variant","description":"Two variants of one product have the same id."},{"type":"string","const":"duplicate_id","description":"An earlier row has the same type and id."},{"type":"string","const":"missing_id","description":"The row has no id: the column that `id` maps is empty, or nothing maps it."},{"type":"string","const":"wrong_value","description":"A value is not one of the attribute's type, such as a list where the attribute takes one value."},{"type":"string","const":"not_a_number","description":"A number or quantity cannot be read, such as \"about two metres\"."},{"type":"string","const":"wrong_unit","description":"A quantity's unit measures something else than the attribute, such as kilograms for a width."}],"description":"What is wrong with a rejected row or value. Codes are stable; the sentence beside them may change."},"Rejection":{"type":"object","additionalProperties":false,"properties":{"row":{"type":"integer","format":"uint64","minimum":0,"description":"The row as the merchant finds it in their file, counted from 1: the line of JSON Lines or delimited text (the\nheader is row 1), the row of a sheet, the item of an XML feed. An entity made of several rows is named by its\nfirst."},"id":{"type":"string","description":"The entity's id, when the row names one."},"column":{"type":"string","description":"The export's column that holds what is wrong, as the header names it; left out for JSON Lines and for the row as\na whole."},"pointer":{"type":"string","description":"A JSON pointer to the field within the entity the row became, such as `/variants/0/price`; empty for the row\nas a whole."},"code":{"$ref":"#/components/schemas/RejectionCode","description":"What is wrong, as a code the panel translates."},"message":{"type":"string","description":"What is wrong, as a sentence."},"value":{"type":"string","description":"The value that is wrong, as the file writes it, such as \"ab 249 €\"; left out when the row as a whole is."}},"required":["row","pointer","code","message"],"description":"A row of the feed, or one value in a row, that was left out, and why."},"ValuesReport":{"type":"object","additionalProperties":false,"properties":{"rejected":{"type":"integer","format":"uint64","minimum":0,"description":"Values left out because they do not fit their attribute; their entity was indexed without them."},"rejections":{"type":"array","items":{"$ref":"#/components/schemas/RejectionGroup"},"description":"The values left out grouped by what is wrong, the most common first, each with its first values as examples."},"new":{"type":"integer","format":"uint64","minimum":0,"description":"Option values attributes.json does not list; each became a value of its own."},"ungrouped":{"type":"integer","format":"uint64","minimum":0,"description":"Values of attributes with color groups that no stage of the built-in colors could place in a group."},"derived":{"type":"array","items":{"$ref":"#/components/schemas/DerivedValue"},"description":"The values whose meaning the import derived, the most common first."},"unstored":{"type":"integer","format":"uint64","minimum":0,"description":"Values and groups attributes.json lists without a slug in a locale: the run made it from the label, so a new\nlabel would move the URLs that hold it. A write through the management API stores it."},"unstored_slugs":{"type":"array","items":{"$ref":"#/components/schemas/UnstoredSlug"},"description":"The first of them, in the order of attributes.json, with the slugs the run made."}},"description":"What the import made of the feed's attribute values beyond reading them as their definitions say."},"DerivedValue":{"type":"object","additionalProperties":false,"properties":{"attribute":{"$ref":"#/components/schemas/AttributeCode"},"value":{"$ref":"#/components/schemas/ValueCode"},"label":{"type":"string","description":"The feed's spelling as first seen, which shoppers see for a value attributes.json does not list."},"listed":{"type":"boolean","description":"attributes.json lists the value without a group, and the built-in colors gave it one."},"count":{"type":"integer","format":"uint64","minimum":0,"description":"Entities and variants that carry the value."},"groups":{"type":"array","items":{"$ref":"#/components/schemas/ValueCode"},"description":"The groups it shows under; a value between two colors, such as petrol, may have two."},"placed":{"description":"How the built-in colors chose the group, for attributes with color groups.","$ref":"#/components/schemas/Placement"}},"required":["attribute","value","label","count"],"description":"An option value whose meaning the import derived instead of reading it from attributes.json: a value the feed\nbrought, or a color group the built-in colors gave a value."},"Placement":{"oneOf":[{"type":"object","properties":{"by":{"type":"string","const":"name"}},"required":["by"],"additionalProperties":false,"description":"The whole name is a color the built-in table knows, such as \"Anthrazit\"."},{"type":"object","additionalProperties":false,"properties":{"word":{"type":"string"},"by":{"type":"string","const":"word"}},"required":["by","word"],"description":"The color word inside the name that decides, such as \"basalt\" in \"Basaltgrau\"."},{"type":"object","additionalProperties":false,"properties":{"swatch":{"$ref":"#/components/schemas/Swatch"},"distance":{"type":"number","format":"double"},"by":{"type":"string","const":"swatch"}},"required":["by","swatch","distance"],"description":"The group whose center lies nearest to the value's swatch in OKLab, at this distance."},{"type":"object","properties":{"by":{"type":"string","const":"nothing"}},"required":["by"],"additionalProperties":false,"description":"No stage placed it: the name holds no color word, or words of two groups, and the value has no swatch."}],"description":"The stage of the built-in colors that placed a value in a group, or that none did."},"UnstoredSlug":{"type":"object","additionalProperties":false,"properties":{"attribute":{"$ref":"#/components/schemas/AttributeCode"},"value":{"$ref":"#/components/schemas/ValueCode","description":"The value's or group's code."},"slug":{"$ref":"#/components/schemas/Localized_UrlSlug"}},"required":["attribute","value","slug"],"description":"A listed value or group whose slug the run made from its label, because attributes.json stores none."},"MappingSuggestions":{"type":"object","additionalProperties":false,"properties":{"model":{"type":"string","description":"The model that answered, as the AI gateway names it, such as `typesafe-ai/jev`."},"columns":{"type":"array","items":{"$ref":"#/components/schemas/ColumnSuggestion"},"description":"One per column that holds values, in the file's order."},"mapping":{"description":"The import's mapping with the taken suggestions in it, as `feed.json` holds it; left out when none was taken.","$ref":"#/components/schemas/Feed"}},"required":["model","columns"],"description":"The model's suggestions for an import's columns, asked once per import and kept with it."},"ColumnSuggestion":{"type":"object","additionalProperties":false,"properties":{"column":{"type":"string","description":"The column's name as the header writes it."},"target":{"$ref":"#/components/schemas/Target","description":"The model's choice among the fields, the release's attributes and signals, an attribute of the column's own,\nand `ignore`."},"probability":{"type":"number","format":"double","description":"The model's probability for its choice.","minimum":0,"maximum":1},"taken":{"type":"boolean","description":"Taken into the mapping without asking: the model is nearly certain, and the import could only guess the column,\nplaced it by a shared prefix or left it out, or only a column's name chose the field. A column whose field\nanother column took is taken to the model's choice for it, so no field is filled twice."}},"required":["column","target","probability"],"description":"Where the model would send one column, and how probable it finds that."},"ExportsToCompare":{"type":"object","additionalProperties":false,"properties":{"draft":{"$ref":"#/components/schemas/KeptExport"},"live":{"description":"What searches read now; left out before anything went live.","$ref":"#/components/schemas/KeptExport"}},"required":["draft"],"description":"An import's export against the live one, each with the release files it is read with."},"KeptExport":{"type":"object","additionalProperties":false,"properties":{"snapshot":{"$ref":"#/components/schemas/SnapshotId"},"files":{"type":"object","additionalProperties":{"type":"string"}}},"required":["snapshot","files"],"description":"A kept export and the release files to read it with, by name."},"ImportChanges":{"type":"object","additionalProperties":false,"properties":{"products":{"$ref":"#/components/schemas/ProductChanges"},"attributes":{"$ref":"#/components/schemas/Changed","description":"Attributes the export's columns go to by name, new and gone; attributes named inside cells, such as name and\nvalue pairs, are not compared."},"facets":{"$ref":"#/components/schemas/Changed2","description":"The filters of search pages, as `categories.json` lists them."}},"required":["products","attributes","facets"],"description":"What publishing an import changes for shoppers, against the live catalog and release."},"ProductChanges":{"type":"object","additionalProperties":false,"properties":{"draft":{"type":"integer","format":"uint64","minimum":0,"description":"Products the import's export names."},"live":{"type":"integer","format":"uint64","minimum":0,"description":"Products the live export names; 0 when nothing is live."},"added":{"type":"integer","format":"uint64","minimum":0,"description":"In the draft, not live."},"removed":{"type":"integer","format":"uint64","minimum":0,"description":"Live, not in the draft."},"added_examples":{"type":"array","items":{"type":"string"},"description":"The first ids added, up to `LISTED_CHANGES`."},"removed_examples":{"type":"array","items":{"type":"string"},"description":"The first ids removed, up to `LISTED_CHANGES`."},"needs_confirmation":{"type":"boolean","description":"The draft removes so large a share of the live products that publishing it needs the number confirmed: a broken\nexport must not empty a shop."}},"required":["draft","live","added","removed"],"description":"Products as the exports name them: an export's product is its group, or the row's id where it has none. A row left\nout by a problem still names its product, so the problems are counted apart."},"Changed":{"type":"object","additionalProperties":false,"properties":{"added":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"}},"removed":{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"}}},"required":["added","removed"],"description":"What a draft adds and what it removes, against what is live; both lists are always written."},"Changed2":{"type":"object","additionalProperties":false,"properties":{"added":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"}},"removed":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"}}},"required":["added","removed"],"description":"What a draft adds and what it removes, against what is live; both lists are always written."},"PublicationRequest":{"type":"object","additionalProperties":false,"properties":{"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog the run would index: the current one for a release, the import's own export for an import."},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"The whole release as files by name, as `ValidateRelease` takes them."}},"required":["snapshot","files"],"description":"The release and catalog to decide a publish for, as the Query API takes them."},"Publication":{"type":"object","additionalProperties":false,"properties":{"course":{"$ref":"#/components/schemas/Course"},"estimate_seconds":{"type":"integer","format":"uint64","minimum":0,"description":"For a rebuild: about how many seconds it takes, from the time of the last rebuild. It grows with the catalog, so\na much larger catalog takes longer. Left out for any other way, and before a first rebuild."}},"required":["course"],"description":"What publishing a release does, decided before it is published, by the same decision the indexer takes."},"CategoryTree":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that is live."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog the categories are read from."},"categories":{"type":"array","items":{"$ref":"#/components/schemas/CategorySummary"},"description":"Every category, by id. Each names its parent, so a client arranges the tree itself; a parent does not\nnecessarily come before its children."}},"required":["release","snapshot","categories"],"description":"The categories of the live catalog, as `GET /internal/categories` and `GET /api/v1/categories` answer them."},"CategorySummary":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/CategoryId"},"parent":{"description":"The parent node; left out for a root.","$ref":"#/components/schemas/CategoryId"},"title":{"description":"What the node is called per locale; left out where the catalog gives no title.","$ref":"#/components/schemas/Localized_string"},"path":{"description":"The page's path in the merchant's shop per locale, such as `/wohnen/sofas`; left out where the catalog gives\nnone.","$ref":"#/components/schemas/Localized_string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/ChannelId"},"description":"The channels the node exists in; left out where it exists in every channel."},"lists":{"$ref":"#/components/schemas/TypeCode","description":"The type its page lists: the type with offers most of the entities in it or below it are, `product` where it\nholds nothing."},"listed":{"type":"integer","format":"uint64","minimum":0,"description":"How many entities of that type sit in it or below it."}},"required":["id","lists","listed"],"description":"One category node of the live catalog."},"PageSettingsRequest":{"type":"object","additionalProperties":false,"properties":{"category":{"description":"The category whose page it is, such as `wohnen/sofas`. Default: the search page.","$ref":"#/components/schemas/CategoryId"},"channel":{"description":"The channel. Default: the release's first channel.","$ref":"#/components/schemas/ChannelId"},"locale":{"description":"The locale the labels and the address are in. Default: the channel's first locale.","$ref":"#/components/schemas/Locale"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Release files by name in place of the live release's own, such as a draft's `categories.json`: the settings\nare read from them, completed with what the live index run derived, so a draft's page is seen before it is\npublished. What the run derived stays as it derived it until the draft is published, also where the draft\nchanges what it was derived from, such as the default facets the categories follow."}},"description":"The page whose settings to show."},"PageSettings":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that decided: the live release, or the draft the files make."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog the categories are read from."},"channel":{"$ref":"#/components/schemas/ChannelId"},"locale":{"$ref":"#/components/schemas/Locale"},"category":{"description":"The category whose page it is; left out for the search page.","$ref":"#/components/schemas/CategoryId"},"title":{"type":"string","description":"The category's title in the locale; left out for the search page."},"url":{"type":"string","description":"Where the shop shows the page in the locale: the category's path, or the search page's. Left out where the\ncategory has no page in the channel."},"facets":{"$ref":"#/components/schemas/FacetSettings"},"groups":{"$ref":"#/components/schemas/GroupSettings"},"sort":{"$ref":"#/components/schemas/SortSettings"}},"required":["release","snapshot","channel","locale","facets","groups","sort"],"description":"A page's settings as a search on it reads them, each with where it comes from."},"FacetSettings":{"type":"object","additionalProperties":false,"properties":{"origin":{"$ref":"#/components/schemas/SettingOrigin","description":"The list is taken whole from one place."},"facets":{"type":"array","items":{"$ref":"#/components/schemas/FacetSetting"}}},"required":["origin","facets"],"description":"The facets a page shows, in display order, a group's facets together."},"SettingOrigin":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"derived":{"type":"boolean","description":"The release leaves it out, so the index run derived it from the catalog."},"from":{"type":"string","const":"page"}},"required":["from"],"description":"The page's own category sets it."},{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"title":{"type":"string","description":"The category's title in the locale, or its id where the catalog gives none."},"derived":{"type":"boolean","description":"The release leaves it out, so the index run derived it from the catalog."},"from":{"type":"string","const":"category"}},"required":["from","category","title"],"description":"The nearest category above the page that sets it, which the page inherits it from."},{"type":"object","additionalProperties":false,"properties":{"derived":{"type":"boolean","description":"The release leaves it out, so the index run derived it from the catalog."},"from":{"type":"string","const":"default"}},"required":["from"],"description":"The default layout, which every page that sets none inherits."},{"type":"object","properties":{"from":{"type":"string","const":"built_in"}},"required":["from"],"additionalProperties":false,"description":"Nothing sets it, so the page has none, or starts by relevance."}],"description":"Where a setting of a page comes from. A page takes each setting from the nearest category up the tree that sets\nit, else from the default layout of `categories.json`."},"FacetSetting":{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldCode"},"label":{"type":"string","description":"The field's name in the locale."},"kind":{"$ref":"#/components/schemas/FacetKind","description":"How the facet is shown, its natural kind where the layout names none."},"shown":{"$ref":"#/components/schemas/ShownFacet","description":"How the list writes it, or for a field code alone, how the default layout shows the field."},"shown_as_default":{"type":"boolean","description":"The list names the field alone, so `shown` is how the default layout shows it."},"group":{"description":"The group the page draws it in.","$ref":"#/components/schemas/GroupResult"},"reason":{"description":"Why the index run chose it, where the list is derived.","$ref":"#/components/schemas/DerivedBy"}},"required":["field","label","kind","shown"],"description":"One facet of a page."},"GroupSettings":{"type":"object","additionalProperties":false,"properties":{"origin":{"$ref":"#/components/schemas/SettingOrigin"},"groups":{"type":"array","items":{"$ref":"#/components/schemas/GroupSetting"},"description":"Every group the page's layout sets; one whose page lists fewer than two of its facets is drawn apart."}},"required":["origin","groups"],"description":"The groups of facets a page draws under one heading."},"GroupSetting":{"type":"object","additionalProperties":false,"properties":{"group":{"$ref":"#/components/schemas/GroupCode"},"label":{"type":"string","description":"The heading, in the locale."},"facets":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"}},"display":{"$ref":"#/components/schemas/GroupDisplay"}},"required":["group","label","facets"],"description":"A group of facets as the layout sets it."},"SortSettings":{"type":"object","additionalProperties":false,"properties":{"default":{"$ref":"#/components/schemas/SortDefault","description":"The order the page starts in while the shopper chooses none and types no words."},"options":{"$ref":"#/components/schemas/SortOptions","description":"The orders the page offers, as its answers list them."},"choices":{"type":"array","items":{"$ref":"#/components/schemas/SortOption"},"description":"Every order a page can offer, `relevance` first, in the locale."}},"required":["default","options","choices"],"description":"The order a page starts in and the orders it offers."},"SortDefault":{"type":"object","additionalProperties":false,"properties":{"sort":{"$ref":"#/components/schemas/SortOption"},"origin":{"$ref":"#/components/schemas/SettingOrigin"},"reason":{"description":"Why the index run chose it, where it is derived.","$ref":"#/components/schemas/DerivedBy"},"note":{"type":"string","description":"Why the page starts in another order than the layout names, such as a sort `ranking.json` no longer defines."}},"required":["sort","origin"],"description":"The order a page starts in."},"SortOptions":{"type":"object","additionalProperties":false,"properties":{"origin":{"$ref":"#/components/schemas/SettingOrigin","description":"The list is taken whole from one place."},"reason":{"description":"Why the index run chose them, where they are derived.","$ref":"#/components/schemas/DerivedBy"},"options":{"type":"array","items":{"$ref":"#/components/schemas/SortOption"},"description":"In display order. A page that lists any offers the order it starts in too, marked `default`."}},"required":["origin","options"],"description":"The orders a page offers."},"RulesRequest":{"type":"object","additionalProperties":false,"properties":{"channel":{"description":"The channel the catalog is read in, to find out whether the products a rule names are still sold. Default: the\nrelease's first channel.","$ref":"#/components/schemas/ChannelId"},"locale":{"description":"The locale the products are named in. Default: the channel's first locale.","$ref":"#/components/schemas/Locale"},"time":{"description":"The time the states are as at, so a scheduled rule can be seen on the day it starts. Default: now.","$ref":"#/components/schemas/Timestamp"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Release files by name in place of the live release's own, such as a draft's `rules.json`."}},"description":"The rules to list. Without `files`, they are the live release's."},"RuleList":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that answered: the live release, or the draft the files make."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog the products were looked up in."},"channel":{"$ref":"#/components/schemas/ChannelId"},"locale":{"$ref":"#/components/schemas/Locale"},"time":{"$ref":"#/components/schemas/Timestamp","description":"The time the states are as at."},"rules":{"type":"array","items":{"$ref":"#/components/schemas/ListedRule"}}},"required":["release","snapshot","channel","locale","time","rules"],"description":"The rules of a release in precedence order: the first rule is the highest."},"ListedRule":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/RuleId"},"position":{"type":"integer","format":"uint32","minimum":0,"description":"Its place in the order, counted from 1. The higher rule wins where two effects exclude each other."},"description":{"type":"string","description":"The rule's name, one line."},"summary":{"type":"string","description":"What it does and when, in one line, such as `Pins Vidar Ecksofa to 1 for \"sofa\"`. Products are named by their\ntitle where the catalog holds them, else by their id."},"enabled":{"type":"boolean","description":"A disabled rule is not evaluated."},"origin":{"description":"Where the rule came from; left out for a rule the merchant made.","$ref":"#/components/schemas/Origin"},"surfaces":{"type":"array","items":{"$ref":"#/components/schemas/Surface"},"description":"Where it can act, from its condition: the surfaces it names, else the category page if it names a category, else\nevery surface."},"state":{"$ref":"#/components/schemas/RuleStanding"},"conflicts":{"type":"array","items":{"$ref":"#/components/schemas/RuleConflict"},"description":"What other rules take from it, in rule order; none when nothing does. They come from higher rules, but for a hide, which counts wherever it stands."},"entities":{"type":"array","items":{"$ref":"#/components/schemas/NamedEntity"},"description":"The entities it names, with what the catalog says of each, so an editor shows a pinned product or a banner's\npage by its title and photo. One the catalog no longer holds is marked `gone`: its effects do nothing, and the rule stays as the\nmerchant wrote it."},"fields":{"description":"What an editor sets with fields, when the fields express the whole rule.","$ref":"#/components/schemas/RuleFields"},"grid":{"description":"The category page whose grid this rule is, which that page arranges: the highest rule that is enabled, acts on\nthat page alone in every channel and at every time, only while nothing is typed, and does nothing but pin within\nthe first `GRID_SIZE` positions. Left out for every other rule, a lower one of the same shape included.","$ref":"#/components/schemas/CategoryId"},"source":{"description":"The rule as `rules.json` writes it, read only: left out where `fields` hold the whole rule. The management API\nchanges such a rule through the draft's files.","$ref":"#/components/schemas/Rule"}},"required":["id","position","description","summary","enabled","surfaces","state"],"description":"One rule with what a merchant wants to know of it."},"RuleStanding":{"type":"object","oneOf":[{"type":"object","properties":{"until":{"description":"When its window closes; left out for a rule without an end.","$ref":"#/components/schemas/Timestamp"},"id":{"type":"string","const":"active"}},"required":["id"],"description":"It applies to the requests its condition matches."},{"type":"object","properties":{"from":{"$ref":"#/components/schemas/Timestamp"},"until":{"$ref":"#/components/schemas/Timestamp"},"id":{"type":"string","const":"scheduled"}},"required":["id","from"],"description":"Its window has not opened yet."},{"type":"object","properties":{"until":{"$ref":"#/components/schemas/Timestamp"},"id":{"type":"string","const":"expired"}},"required":["id","until"],"description":"Its window has closed."},{"type":"object","properties":{"id":{"type":"string","const":"disabled"}},"required":["id"],"description":"It is switched off."},{"type":"object","properties":{"id":{"type":"string","const":"dormant"}},"required":["id"],"description":"It acts on entities alone, and every one of them is `gone` from the catalog, so it does nothing until one comes\nback."}],"description":"A rule's state at the list's time, as data and as a sentence.","properties":{"sentence":{"type":"string","description":"The state in a sentence, such as \"Starts 2026-11-27T00:00:00+01:00 and ends 2026-11-30T00:00:00+01:00.\""}},"required":["sentence"]},"RuleConflict":{"type":"object","oneOf":[{"type":"object","properties":{"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"redirect"}},"required":["id","by"],"description":"The highest redirect wins and ends planning, so this redirect never sends a shopper that the higher one does\nnot."},{"type":"object","properties":{"term":{"type":"string"},"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"term"}},"required":["id","term","by"],"description":"The higher rule decides what the term means."},{"type":"object","properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"pinned"}},"required":["id","entity","by"],"description":"The higher rule pins the same entity, and its pin counts."},{"type":"object","properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"position":{"type":"integer","format":"uint32","minimum":0},"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"slot"}},"required":["id","entity","position","by"],"description":"The higher rule pins a position this pin asks for, so this pin moves to the next free one."},{"type":"object","properties":{"position":{"type":"integer","format":"uint32","minimum":0},"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"placement"}},"required":["id","position","by"],"description":"The higher rule places a banner at a grid position this placement asks for, so this one moves to the next free\none."},{"type":"object","properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"hidden"}},"required":["id","entity","by"],"description":"The other rule hides the entity, and hide beats pin, whichever rule is higher."},{"type":"object","properties":{"target":{"type":"string"},"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"strength"}},"required":["id","target","by"],"description":"The higher rule boosts or buries the same target, and its strength counts. `target` is the target as a trace\nsays it, such as `brand is \"bosch\"`."},{"type":"object","properties":{"by":{"$ref":"#/components/schemas/RuleId"},"id":{"type":"string","const":"sections"}},"required":["id","by"],"description":"The higher rule sets the order of the sections."}],"description":"What another rule takes from a rule where their effects exclude each other, as data and as a\nsentence. It holds where both rules can match one request: conditions that cannot meet, such as two different\nquery phrases, never conflict.","properties":{"sentence":{"type":"string","description":"The conflict in a sentence, such as \"A rule above it pins product:SOFA-VIDAR to 1 as well, so this pin moves to\n2.\""}},"required":["sentence"]},"NamedEntity":{"type":"object","additionalProperties":false,"properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"title":{"type":"string","description":"Its title; left out where the catalog holds none, or no longer holds the entity."},"image":{"type":"string","description":"Its first photo's URL, as the catalog gives it; left out where there is none."},"gone":{"type":"boolean","description":"The catalog no longer holds it, or does not sell it in the channel."}},"required":["entity"],"description":"An entity a rule names, as the catalog holds it in the list's channel and locale."},"RuleFields":{"type":"object","additionalProperties":false,"properties":{"when":{"$ref":"#/components/schemas/WhenFields"},"then":{"$ref":"#/components/schemas/ThenFields"},"window":{"description":"When the rule is active, checked against the time in the request. Left out for a rule without a window.","$ref":"#/components/schemas/TimeWindow"}},"required":["when","then"],"description":"The fields of a rule an editor sets."},"WhenFields":{"type":"object","additionalProperties":false,"properties":{"trigger":{"$ref":"#/components/schemas/Trigger"},"channel":{"description":"Only in this channel. Left out for every channel.","$ref":"#/components/schemas/ChannelId"}},"required":["trigger"],"description":"When a rule applies."},"Trigger":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"always"}},"required":["kind"],"additionalProperties":false,"description":"Every search and category page."},{"type":"object","additionalProperties":false,"properties":{"matching":{"$ref":"#/components/schemas/PhraseMatching"},"phrases":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"query"}},"required":["kind","matching","phrases"],"description":"Searches whose words are one of the phrases, or contain one. Matching is literal after normalization: no typos,\nsynonyms or plurals."},{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"below":{"type":"boolean","description":"The pages below it too. Default: only this page."},"without_words":{"type":"boolean","description":"Only while no words are typed on the page, as a grid of its top positions is."},"kind":{"type":"string","const":"category"}},"required":["kind","category"],"description":"A category page."}],"description":"What sets a rule off."},"PhraseMatching":{"oneOf":[{"type":"string","const":"is","description":"The query is exactly the phrase."},{"type":"string","const":"contains","description":"The query contains the phrase as a run of whole words."}]},"ThenFields":{"type":"object","additionalProperties":false,"properties":{"pin":{"type":"array","items":{"$ref":"#/components/schemas/Pin"},"description":"Chosen entities at fixed positions, counted from 1."},"hide":{"type":"array","items":{"$ref":"#/components/schemas/Subject"},"description":"Takes entities out of the results. Hide beats pin."},"boost":{"type":"array","items":{"$ref":"#/components/schemas/Shifted"},"description":"Moves entities up, within relevance."},"bury":{"type":"array","items":{"$ref":"#/components/schemas/Shifted"},"description":"Moves entities down, within relevance."},"redirect":{"description":"Sends the shopper elsewhere.","$ref":"#/components/schemas/RedirectFields"},"place":{"type":"array","items":{"$ref":"#/components/schemas/BannerPlacement"},"description":"Banners and teasers above, among or below the results, each with its words in every locale the release serves."}},"description":"What a rule does. At least one effect."},"Subject":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"entities":{"type":"array","items":{"$ref":"#/components/schemas/EntityRef"}},"of":{"type":"string","const":"entities"}},"required":["of","entities"],"description":"Chosen products or other entities."},{"type":"object","additionalProperties":false,"properties":{"brand":{"type":"string"},"of":{"type":"string","const":"brand"}},"required":["of","brand"],"description":"Everything of a brand, as the brand facet counts it."},{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"below":{"type":"boolean","description":"The categories below it too. Default: only this one."},"of":{"type":"string","const":"category"}},"required":["of","category"],"description":"Everything in a category."}],"description":"What an effect is about."},"Shifted":{"type":"object","additionalProperties":false,"properties":{"subject":{"$ref":"#/components/schemas/Subject"},"strength":{"$ref":"#/components/schemas/Strength"}},"required":["subject","strength"],"description":"A boost or bury with its named strength."},"RedirectFields":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"page":{"$ref":"#/components/schemas/EntityRef"},"to":{"type":"string","const":"page"}},"required":["to","page"],"description":"A page of the shop: a category, a brand, a product or another entity. Its address follows the catalog."},{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string"},"to":{"type":"string","const":"url"}},"required":["to","url"],"description":"A fixed URL: a path such as `/werkstatt`, or one that starts with `https://`."}],"description":"Where a redirect sends the shopper."},"EditRules":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"string","description":"The text of the draft's `rules.json`; empty where the draft has none."},"edit":{"$ref":"#/components/schemas/RuleEdit"}},"required":["rules","edit"],"description":"A change and the `rules.json` it applies to."},"RuleEdit":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/NewRule"},"edit":{"type":"string","const":"create"}},"required":["edit","rule"]},{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/RuleId"},"change":{"$ref":"#/components/schemas/RuleChange"},"edit":{"type":"string","const":"change"}},"required":["edit","id","change"]},{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/RuleId"},"to":{"$ref":"#/components/schemas/RuleMove"},"edit":{"type":"string","const":"move"}},"required":["edit","id","to"]},{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/RuleId"},"edit":{"type":"string","const":"delete"}},"required":["edit","id"]}],"description":"One change of `rules.json`, which the Query API applies to the file's text."},"NewRule":{"type":"object","additionalProperties":false,"properties":{"id":{"description":"The rule's id. Default: made from the name, and made unique.","$ref":"#/components/schemas/RuleId"},"name":{"type":"string","description":"The rule's name, one line up to 200 characters. Default: what the rule does, in a sentence."},"enabled":{"type":"boolean","description":"Default: on."},"position":{"type":"integer","format":"uint32","minimum":0,"description":"Its place in the order, counted from 1; a number past the end puts it last. Default: first, since a merchant's\nnew rule outranks the older ones."},"fields":{"$ref":"#/components/schemas/RuleFields"}},"required":["fields"],"description":"A rule to add to the draft."},"RuleChange":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"fields":{"description":"The new fields, all of them: the rule's condition, effects and window are replaced. Refused for a rule the\nfields cannot express, since the change would drop what they leave out.","$ref":"#/components/schemas/RuleFields"}},"description":"A change of one rule. What is left out stays as it is."},"RuleMove":{"type":"object","additionalProperties":false,"properties":{"position":{"type":"integer","format":"uint32","minimum":1,"description":"Its new place, counted from 1; a number past the end puts it last."}},"required":["position"],"description":"Where a rule goes in the order."},"EditedRules":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"string"},"rule":{"description":"The rule that was created, changed or moved; left out after a delete.","$ref":"#/components/schemas/RuleId"}},"required":["rules"],"description":"`rules.json` after the change, in canonical form."},"SynonymsRequest":{"type":"object","additionalProperties":false,"properties":{"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Release files by name in place of the live release's own, such as a draft's `synonyms.json`."}},"description":"The synonyms to list. Without `files`, they are the live release's."},"SynonymList":{"type":"object","additionalProperties":false,"properties":{"locales":{"type":"array","items":{"$ref":"#/components/schemas/Locale"},"description":"The languages the channels serve, in channel order: the first is the shop's, and a shop with one needs no\nchoice of language."},"reads":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$":{"$ref":"#/components/schemas/Locale"}},"description":"For each locale the channels serve, the dictionary it reads: its own, else the one of the nearest shorter tag,\nsuch as `de` for `de-CH`. A locale with no dictionary along its tag reads one under its own name once it has\nentries. A client opens the dictionary a shopper's locale reads from here, never by matching tags itself."},"dictionaries":{"type":"array","items":{"$ref":"#/components/schemas/ListedDictionary"},"description":"One dictionary per language the channels serve, then those for a language none serves yet."}},"required":["locales","reads","dictionaries"],"description":"Every language's synonyms."},"ListedDictionary":{"type":"object","additionalProperties":false,"properties":{"locale":{"$ref":"#/components/schemas/Locale"},"dormant":{"type":"boolean","description":"No channel serves the language, so its synonyms wait until one does. A language a channel serves as `de-CH`\nreads the `de` dictionary when it has none of its own."},"entries":{"type":"array","items":{"$ref":"#/components/schemas/ListedSynonym"}}},"required":["locale","entries"],"description":"The entries of one language: its groups, then its one-way entries, then the words never merged, each in file order."},"ListedSynonym":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/SynonymId","description":"Its place in `synonyms.json`, such as `de/groups/0`, which a change or delete names."},"summary":{"type":"string","description":"What it does in one line, such as \"couch finds sofa, not the other way round.\""},"conflicts":{"type":"array","items":{"$ref":"#/components/schemas/SynonymConflict"},"description":"What it clashes with: a word another group holds too, or words a `never` entry keeps apart that it merges."}},"required":["id","summary"],"oneOf":[{"type":"object","description":"Words that mean the same, each way round, such as `[\"couch\", \"sofa\"]`. A word or phrase is in one group at most.","properties":{"words":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"group"}},"required":["kind","words"]},{"type":"object","description":"A word that also finds others, but not the other way round: `läufer` finds `teppich`. A word starts one such\nentry at most.","properties":{"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"one_way"}},"required":["kind","from","to"]},{"type":"object","description":"Words that look alike but must never be merged, such as `[\"sessel\", \"sofa\"]`. They change no search; an entry\nthat would merge them is refused unless the write says `despite_never`.","properties":{"words":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"never"}},"required":["kind","words"]}],"description":"One entry with what a merchant wants to know of it."},"SynonymConflict":{"type":"object","oneOf":[{"type":"object","properties":{"id":{"type":"string","const":"too_few"}},"required":["id"],"description":"It names fewer than two different words; a one-way entry names a word and at least one other it finds."},{"type":"object","properties":{"word":{"type":"string"},"entry":{"$ref":"#/components/schemas/SynonymId"},"words":{"type":"array","items":{"type":"string"}},"id":{"type":"string","const":"taken"}},"required":["id","word","entry","words"],"description":"The word is in another group already, or starts another one-way entry: `entry` is the one to add to instead,\nwith its words."},{"type":"object","properties":{"words":{"type":"array","items":{"type":"string"}},"entry":{"$ref":"#/components/schemas/SynonymId"},"id":{"type":"string","const":"never"}},"required":["id","words","entry"],"description":"It merges two words the `never` entry `entry` keeps apart; for a `never` entry, `entry` is the one that merges\nthem."}],"description":"A clash as data and as one sentence, such as \"“sofa” is in the group couch, sofa, settee already.\"","properties":{"sentence":{"type":"string"}},"required":["sentence"]},"EditSynonyms":{"type":"object","additionalProperties":false,"properties":{"synonyms":{"type":"string","description":"The text of the draft's `synonyms.json`; empty where the draft has none."},"edit":{"$ref":"#/components/schemas/SynonymEdit"}},"required":["synonyms","edit"],"description":"A change and the `synonyms.json` it applies to."},"SynonymEdit":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"locale":{"$ref":"#/components/schemas/Locale"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/SynonymEntry"},"minItems":1,"maxItems":1000},"despite_never":{"type":"boolean","description":"Writes an entry that merges words a `never` entry keeps apart, which is refused otherwise."},"edit":{"type":"string","const":"add"}},"required":["edit","locale","entries"],"description":"Adds entries to one language, in order, each after its own kind's: those that clash are refused, each with why,\nand the others are written, as pasting many lines at once needs."},{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/SynonymId"},"entry":{"$ref":"#/components/schemas/SynonymEntry"},"despite_never":{"type":"boolean"},"edit":{"type":"string","const":"change"}},"required":["edit","id","entry"],"description":"Replaces an entry's words. It keeps its place, unless its kind changes, which moves it after the entries of its\nnew kind and so gives it another id."},{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/SynonymId"},"edit":{"type":"string","const":"delete"}},"required":["edit","id"],"description":"Removes an entry; the entries after it of its kind move up one place, and so change their ids."}],"description":"One change of `synonyms.json`, which the Query API applies to the file's text."},"SynonymEntry":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"words":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"group"}},"required":["kind","words"],"description":"Words that mean the same, each way round, such as `[\"couch\", \"sofa\"]`. A word or phrase is in one group at most."},{"type":"object","additionalProperties":false,"properties":{"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"one_way"}},"required":["kind","from","to"],"description":"A word that also finds others, but not the other way round: `läufer` finds `teppich`. A word starts one such\nentry at most."},{"type":"object","additionalProperties":false,"properties":{"words":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","const":"never"}},"required":["kind","words"],"description":"Words that look alike but must never be merged, such as `[\"sessel\", \"sofa\"]`. They change no search; an entry\nthat would merge them is refused unless the write says `despite_never`."}],"description":"One entry of a language's synonyms."},"EditedSynonyms":{"type":"object","additionalProperties":false,"properties":{"synonyms":{"type":"string"},"written":{"type":"array","items":{"$ref":"#/components/schemas/SynonymId"},"description":"The entries added or changed, in the order sent; none after a delete."},"refused":{"type":"array","items":{"$ref":"#/components/schemas/RefusedSynonym"},"description":"The entries of an add that were refused, each with why."}},"required":["synonyms"],"description":"`synonyms.json` after the change, in canonical form."},"RefusedSynonym":{"type":"object","additionalProperties":false,"properties":{"index":{"type":"integer","format":"uint32","minimum":0,"description":"Its place among the entries sent, counted from 0."},"entry":{"$ref":"#/components/schemas/SynonymEntry","description":"The entry as it would have been written: trimmed, lowercased and each word once."},"conflicts":{"type":"array","items":{"$ref":"#/components/schemas/SynonymConflict"}}},"required":["index","entry","conflicts"],"description":"An entry that was not written, and why."},"SynonymsRefused":{"type":"object","description":"A write of synonyms that wrote nothing: a problem, and where every entry clashes, each entry with why.","properties":{"type":{"type":"string","description":"A URI naming the kind of problem."},"title":{"type":"string","description":"The status's own phrase, such as `Bad Request`."},"status":{"type":"integer","format":"uint16","minimum":0,"maximum":65535,"description":"The answer's HTTP status."},"detail":{"type":"string","description":"What happened and what to do next, for the person who can fix it."},"violations":{"type":"array","items":{"$ref":"#/components/schemas/Violation"},"description":"For invalid input, every value that is wrong."},"refused":{"type":"array","items":{"$ref":"#/components/schemas/RefusedSynonym"},"description":"The entries refused for their clashes; left out where the problem is another, such as a file that cannot be\nread."}},"required":["type","title","status"]},"CheckSynonym":{"type":"object","properties":{"locale":{"$ref":"#/components/schemas/Locale"},"channel":{"description":"The channel the words are searched in. Default: the first channel that serves a locale reading this language's\nsynonyms, such as `de-CH` for `de`.","$ref":"#/components/schemas/ChannelId"},"entry":{"$ref":"#/components/schemas/SynonymEntry"},"id":{"description":"The entry being changed, whose own words clash with nothing.","$ref":"#/components/schemas/SynonymId"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Release files by name in place of the live release's own, such as a draft's `synonyms.json`."}},"required":["locale","entry"],"description":"An entry to check against the synonyms of a release's files over the live release."},"SynonymCheck":{"type":"object","additionalProperties":false,"properties":{"channel":{"$ref":"#/components/schemas/ChannelId"},"locale":{"$ref":"#/components/schemas/Locale","description":"The locale the words were searched in."},"entry":{"$ref":"#/components/schemas/SynonymEntry","description":"The entry as it would be written: trimmed, lowercased and each word once."},"words":{"type":"array","items":{"$ref":"#/components/schemas/WordFound"},"description":"Each word with what a shopper's search for it finds now, with the live synonyms, in the entry's order."},"conflicts":{"type":"array","items":{"$ref":"#/components/schemas/SynonymConflict"},"description":"Why writing it would be refused, as an add says; none when it can be written."}},"required":["channel","locale","entry","words"],"description":"What an entry's words find today and what writing it would clash with. Nothing is written."},"WordFound":{"type":"object","additionalProperties":false,"properties":{"word":{"type":"string"},"total":{"type":"integer","format":"uint64","minimum":0,"description":"How many results the search page lists for it."}},"required":["word","total"],"description":"A word and what searching for it finds."},"SearchSettingsRequest":{"type":"object","additionalProperties":false,"properties":{"locale":{"description":"The locale the labels are in. Default: the first channel's first locale.","$ref":"#/components/schemas/Locale"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Release files by name in place of the live release's own, such as a draft's `types.json`, completed with what\nthe live index run derived, as the draft's run would derive it from the same catalog."}},"description":"Whose search settings to show. Without `files`, they are the live release's."},"SearchSettings":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that decided: the live release, or the draft the files make."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog the derived settings come from."},"locale":{"$ref":"#/components/schemas/Locale"},"types":{"type":"array","items":{"$ref":"#/components/schemas/TypeSearch"},"description":"In the order of `types.json`."}},"required":["release","snapshot","locale","types"],"description":"How every type of the release is searched."},"TypeSearch":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"label":{"type":"string","description":"The type's name in the locale."},"fields":{"$ref":"#/components/schemas/SearchedFields"},"addable":{"type":"array","items":{"$ref":"#/components/schemas/SearchField"},"description":"The fields it could search beside those: the built-in text fields, then every text, identifier and option\nattribute, in the order of `attributes.json`."},"typos":{"$ref":"#/components/schemas/TypoSettings"},"exempt":{"$ref":"#/components/schemas/ExemptAttributes"}},"required":["type","label","fields","addable","typos","exempt"],"description":"How one type is searched."},"SearchedFields":{"type":"object","additionalProperties":false,"properties":{"source":{"$ref":"#/components/schemas/SettingSource"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/SearchField"}}},"required":["source","fields"],"description":"The fields a type searches, in priority order: a word found in an earlier field ranks a hit higher."},"SettingSource":{"oneOf":[{"type":"string","const":"set","description":"The merchant set it, in the release's or the draft's `types.json`."},{"type":"string","const":"derived","description":"The release leaves it out, so the index run derived it from the catalog: the import's default."},{"type":"string","const":"built_in","description":"Nothing sets it: OrbSearch's own default."}],"description":"Where a search setting comes from."},"SearchField":{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldPath","description":"As `types.json` names it, such as `title` or `attributes.mpn`."},"label":{"type":"string","description":"Its name in the locale."}},"required":["field","label"],"description":"A field a type can search."},"TypoSettings":{"type":"object","additionalProperties":false,"properties":{"source":{"$ref":"#/components/schemas/SettingSource","description":"Set, or built in; never derived."},"enabled":{"type":"boolean"},"one_typo":{"type":"integer","format":"uint8","minimum":0,"maximum":255,"description":"The fewest letters a word needs to find with one typo."},"two_typos":{"type":"integer","format":"uint8","minimum":0,"maximum":255,"description":"The fewest letters a word needs to find with two typos."}},"required":["source","enabled","one_typo","two_typos"],"description":"Whether and from how many letters on a type's words find with a typo."},"ExemptAttributes":{"type":"object","additionalProperties":false,"properties":{"source":{"$ref":"#/components/schemas/SettingSource"},"attributes":{"type":"array","items":{"$ref":"#/components/schemas/ExemptAttribute"}},"open":{"type":"array","items":{"$ref":"#/components/schemas/ExemptAttribute"},"description":"The identifier attributes the type searches that the run left open to typos, each with why: what the catalog\ndoes not tell stays open. Only where the list is derived."}},"required":["source","attributes"],"description":"The attributes a type's words find only as written or by their start."},"ExemptAttribute":{"type":"object","additionalProperties":false,"properties":{"attribute":{"$ref":"#/components/schemas/AttributeCode"},"label":{"type":"string","description":"Its name in the locale."},"reason":{"description":"Why the run decided so, where the list is derived.","$ref":"#/components/schemas/DerivedBy"}},"required":["attribute","label"],"description":"An attribute, exempt from typos or left open to them."},"EditSearch":{"type":"object","additionalProperties":false,"properties":{"types":{"type":"string","description":"The text of the draft's `types.json`; empty where it has none, and the change starts from the types the live\nrun derived."},"type":{"$ref":"#/components/schemas/TypeCode"},"change":{"$ref":"#/components/schemas/SearchChange"}},"required":["types","type","change"],"description":"A change of one type and the `types.json` it applies to."},"SearchChange":{"type":"object","additionalProperties":false,"properties":{"fields":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/FieldPath"}},{"type":"null"}],"description":"The searched fields in priority order, all of them, at least one."},"typos":{"oneOf":[{"$ref":"#/components/schemas/TypoTolerance"},{"type":"null"}],"description":"Whether and from how many letters on words find with a typo."},"exempt":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"}},{"type":"null"}],"description":"The attributes exempt from typos, all of them; `[]` exempts none."}},"description":"A change of how one type is searched. A setting left out stays as it is; `null` sets it back to its default, the\nimport's or OrbSearch's."},"EditedSearch":{"type":"object","additionalProperties":false,"properties":{"types":{"type":"string"}},"required":["types"],"description":"`types.json` after the change, in canonical form."},"OfferChanges":{"type":"object","additionalProperties":false,"properties":{"offers":{"type":"array","items":{"$ref":"#/components/schemas/OfferChange"},"description":"At most `LIMITS.offers_per_call`."}},"required":["offers"],"description":"A batch of offer changes, such as `{ \"offers\": [{ \"product\": \"SOFA-MALMO\", \"variant\": \"SOFA-MALMO-GREY\",\n\"channel\": \"de\", \"price\": 899, \"availability\": \"in_stock\" }] }`, each answered on its own."},"OfferChange":{"type":"object","additionalProperties":false,"properties":{"product":{"$ref":"#/components/schemas/EntityId","description":"The product's id in the catalog."},"variant":{"$ref":"#/components/schemas/EntityId"},"channel":{"$ref":"#/components/schemas/ChannelId"},"price":{"type":"number","description":"In the channel's currency, in its major unit."},"sale_price":{"type":"number","description":"The reduced price, valid within `sale_window` if one is given."},"sale_window":{"$ref":"#/components/schemas/TimeWindow"},"end_sale":{"type":"boolean","description":"Ends the sale: the sale price and its window go, and the price holds."},"availability":{"$ref":"#/components/schemas/Availability"},"stock":{"type":"integer","format":"uint32","minimum":0},"delivery_days":{"$ref":"#/components/schemas/DeliveryDays"}},"required":["product","channel"],"description":"A change of one product's offer in one channel. The fields given replace the offer's, the fields left out stay as\nthey are. A product without variants is changed as itself; a product with variants names the variant. A channel the\nproduct has no offer in yet gets one."},"OffersChecked":{"type":"object","additionalProperties":false,"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/OfferVerdict"}}},"required":["rows"],"description":"The Query API's verdicts on a batch of changes: one per change, in the order sent."},"OfferVerdict":{"oneOf":[{"type":"object","properties":{"state":{"type":"string","const":"accepted"}},"required":["state"],"description":"Kept; it reaches search with the revision."},{"type":"object","properties":{"code":{"$ref":"#/components/schemas/OfferRejection"},"message":{"type":"string"},"state":{"type":"string","const":"rejected"}},"required":["state","code","message"],"description":"Left out, and why; the other changes of the batch are kept."}],"description":"What became of one change."},"OfferRejection":{"oneOf":[{"type":"string","const":"unreadable","description":"The change is not an object of the fields above, such as a price written as text."},{"type":"string","const":"unknown_product","description":"The live catalog has no product with this id; a new product arrives with the catalog."},{"type":"string","const":"unknown_variant","description":"The product has no variant with this id, or no variants at all."},{"type":"string","const":"variant_needed","description":"The product has variants, so the change names the one it changes."},{"type":"string","const":"unknown_channel","description":"The channel is not one of the live release."},{"type":"string","const":"invalid_value","description":"A value no offer can hold, such as a negative price, a sale window that ends before it starts, delivery days\nwhose `min` is above their `max`, or a sale price beside `end_sale`."},{"type":"string","const":"nothing_changed","description":"The change names no field to change."}],"description":"Why a change was left out. Codes are stable; the sentence beside them may change."},"PagePreview":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","description":"The URL as `resolve` reads it: a path with its query string, or a whole URL, whose scheme and host are ignored."},"channel":{"description":"The channel. Default: the release's first channel.","$ref":"#/components/schemas/ChannelId"},"locale":{"description":"The locale. Default: the channel's first locale.","$ref":"#/components/schemas/Locale"},"per_page":{"type":"integer","format":"uint32","minimum":1,"description":"Hits per page of a listing, up to the limit `per_page`. Default: 24.","maximum":100},"context":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"type":"string"}},"description":"Declared context keys and their values, as a search request carries them, such as `{ \"customer_group\": \"b2b\" }`."},"time":{"description":"The time the page is answered at. Default: now.","$ref":"#/components/schemas/Timestamp"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Release files by name that replace the live release's own for this preview alone, such as a draft's\n`categories.json`: the page is planned with them over the live catalog and index, so a draft's facets, sorting\nand URLs can be seen before it is published. What a file changes in the compiled index (a new attribute, a\nrule's pins) is not in the index until it is published, so such a preview shows the page as the live index can\nanswer it."}},"required":["url"],"description":"A URL to preview as a shopper would meet it in a context and at a time, such as\n`{ \"url\": \"/reifen?saison=winter\", \"channel\": \"de\", \"time\": \"2026-11-27T00:00:00+01:00\" }`. The page always comes\nwith its results and traces."},"HitsToCompare":{"type":"object","additionalProperties":false,"properties":{"trace":{"$ref":"#/components/schemas/Trace"},"type":{"$ref":"#/components/schemas/TypeCode","description":"The section both hits are in."},"positions":{"type":"array","items":{"type":"integer","format":"uint32","minimum":0},"minItems":2,"maxItems":2,"description":"Their places on the page, counted from 1, in either order."}},"required":["trace","type","positions"],"description":"Two hits of a trace's rank step to compare: which sits above the other, and why. The trace is the one a debug or\npreview response carried, sent back whole, so the answer is about exactly what was shown."},"HitComparison":{"type":"object","additionalProperties":false,"properties":{"above":{"$ref":"#/components/schemas/HitAt"},"below":{"$ref":"#/components/schemas/HitAt"},"sentence":{"type":"string","description":"The first thing that separated them, in a sentence."},"separated":{"$ref":"#/components/schemas/Separation"}},"required":["above","below","sentence","separated"],"description":"Why one hit sits above another of the same section."},"HitAt":{"type":"object","additionalProperties":false,"properties":{"entity":{"$ref":"#/components/schemas/EntityRef"},"variant":{"$ref":"#/components/schemas/EntityId"},"position":{"type":"integer","format":"uint32","minimum":0}},"required":["entity","position"],"description":"A hit of a section and its place on the page."},"Separation":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string"},"position":{"type":"integer","format":"uint32","minimum":0},"pinned":{"$ref":"#/components/schemas/Side"},"by":{"type":"string","const":"pin"}},"required":["by","rule","description","position","pinned"],"description":"A rule pinned one of them."},{"type":"object","additionalProperties":false,"properties":{"above":{"type":"array","items":{"$ref":"#/components/schemas/Weight"}},"below":{"type":"array","items":{"$ref":"#/components/schemas/Weight"}},"by":{"type":"string","const":"weight"}},"required":["by"],"description":"Boosts and buries weighed them apart: they count before every criterion but a better match of the query's words,\nand the lower one matches those as well or better."},{"type":"object","additionalProperties":false,"properties":{"above":{"$ref":"#/components/schemas/Criterion"},"below":{"$ref":"#/components/schemas/Criterion"},"by":{"type":"string","const":"criterion"}},"required":["by","above","below"],"description":"The first criterion they differ in, with each one's value."},{"type":"object","properties":{"by":{"type":"string","const":"tie"}},"required":["by"],"additionalProperties":false,"description":"They are equal in everything the engine compares, so it keeps the order of its index."},{"type":"object","properties":{"by":{"type":"string","const":"unknown"}},"required":["by"],"additionalProperties":false,"description":"By everything the trace records, the lower one comes first; what put the other above is not recorded."}],"description":"What put one hit above the other, in the order the engine decides: pins, then boosts and buries against the text,\nthen the first criterion they differ in."},"Side":{"type":"string","enum":["above","below"]},"EntityToFind":{"type":"object","additionalProperties":false,"properties":{"request":{"$ref":"#/components/schemas/SearchRequest","description":"The request of the page, as a preview's trace holds it with its time, so the answer is about the page shown."},"entity":{"$ref":"#/components/schemas/EntityRef"}},"required":["request","entity"],"description":"An entity to find on the page a request answers, such as\n`{ \"request\": { \"query\": \"ecksofa\" }, \"entity\": \"product:SOFA-MALMO\" }`."},"Finding":{"type":"object","additionalProperties":false,"properties":{"release":{"$ref":"#/components/schemas/ReleaseId","description":"The configuration that answered."},"snapshot":{"$ref":"#/components/schemas/SnapshotId","description":"The catalog that was searched."},"entity":{"$ref":"#/components/schemas/EntityRef"},"passed":{"type":"array","items":{"$ref":"#/components/schemas/PassedStage"},"description":"The stages it passed, in the order a search runs them."},"verdict":{"$ref":"#/components/schemas/Verdict"},"sentence":{"type":"string","description":"The verdict in a sentence, ending in what to do."},"next":{"type":"array","items":{"$ref":"#/components/schemas/NextStep"},"description":"What the merchant can do to show it on the page, the likeliest first; none when it is there."},"round_trip_ms":{"type":"number","format":"double","description":"The engine call that walked the stages, beside the page's own search: from sending it to reading its answer, in\nmilliseconds. Debug only; no shopper's search makes it."}},"required":["release","snapshot","entity","verdict","sentence","round_trip_ms"],"description":"Where the entity is for the request, or the first stage that kept it off the page, and what to do about it."},"PassedStage":{"type":"object","additionalProperties":false,"properties":{"stage":{"$ref":"#/components/schemas/Stage"},"sentence":{"type":"string","description":"Such as \"It is sold in the channel de.\""}},"required":["stage","sentence"],"description":"A stage the entity passed, in a sentence."},"Stage":{"oneOf":[{"type":"string","const":"index","description":"The live index holds it."},{"type":"string","const":"section","description":"The page searches its type."},{"type":"string","const":"channel","description":"It is sold in the channel."},{"type":"string","const":"assortment","description":"It is in the channel's assortment."},{"type":"string","const":"category","description":"It is in the page's category, or the one the query named."},{"type":"string","const":"rules","description":"No rule's filter or hide removes it."},{"type":"string","const":"filters","description":"It matches the filters the shopper chose and the query named."},{"type":"string","const":"text","description":"The query's words find it."},{"type":"string","const":"rank","description":"Its place among the hits."}],"description":"The stages a search passes an entity through on its way onto a page, in order."},"Verdict":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"overlays":{"type":"array","items":{"$ref":"#/components/schemas/Slug"}},"stage":{"type":"string","const":"index"}},"required":["stage"],"description":"The live index holds no entity of this type and id: the catalog snapshot lacks it, or a hide overlay removes it\nfrom every search. `overlays` are the release's hide overlays of its type that name it or may match it."},{"type":"object","properties":{"stage":{"type":"string","const":"section"}},"required":["stage"],"additionalProperties":false,"description":"The page searches no section of its type: a category page and a search without words list one type alone."},{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string"},"url":{"type":"string"},"stage":{"type":"string","const":"redirect"}},"required":["stage","rule","description","url"],"description":"A rule's redirect sends the shopper elsewhere, so the page searches nothing."},{"type":"object","additionalProperties":false,"properties":{"channel":{"$ref":"#/components/schemas/ChannelId"},"stage":{"type":"string","const":"channel"}},"required":["stage","channel"],"description":"It is not sold in the channel: no offer of it is there."},{"type":"object","additionalProperties":false,"properties":{"channel":{"$ref":"#/components/schemas/ChannelId"},"where":{"$ref":"#/components/schemas/Selector"},"stage":{"type":"string","const":"assortment"}},"required":["stage","channel","where"],"description":"It has an offer in the channel, but the channel's assortment leaves it out: it does not match the selector."},{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"words":{"type":"string","description":"The query's words named the category; otherwise it is the page's own."},"stage":{"type":"string","const":"category"}},"required":["stage","category"],"description":"It is not in the category the page shows, or the one the query named."},{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string"},"where":{"$ref":"#/components/schemas/Selector"},"stage":{"type":"string","const":"rule_filter"}},"required":["stage","rule","description","where"],"description":"A rule's filter narrows the section to what it is not."},{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string"},"where":{"$ref":"#/components/schemas/Selector"},"stage":{"type":"string","const":"hidden"}},"required":["stage","rule","description"],"description":"A rule hides it: by its id, or by a selector it matches."},{"type":"object","additionalProperties":false,"properties":{"where":{"$ref":"#/components/schemas/Selector","description":"With exactly one field or `category`, as the chip writes it."},"source":{"$ref":"#/components/schemas/FilterSource"},"words":{"type":"string","description":"The query's words that named it."},"stage":{"type":"string","const":"filter"}},"required":["stage","where","source"],"description":"A filter the shopper chose, or the query's words named, keeps what it is not."},{"type":"object","additionalProperties":false,"properties":{"where":{"$ref":"#/components/schemas/Selector"},"stage":{"type":"string","const":"together"}},"required":["stage","where"],"description":"It has each chosen value in some variant, but no variant has them all."},{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string","description":"The text the section searched, after rules and what the query named took their words."},"matched":{"type":"array","items":{"$ref":"#/components/schemas/MatchedWord"},"description":"The words that find it alone, with the fields they are in and the synonym that found them."},"missing":{"type":"array","items":{"type":"string"},"description":"The words no field of it holds, as typed, through a synonym or with a typo."},"held":{"type":"array","items":{"$ref":"#/components/schemas/HeldWord"},"description":"Missing words a field of it holds where the search does not look for them: a field its type does not\nsearch, or an attribute exempt from typos that holds the word with a typo."},"stage":{"type":"string","const":"text"}},"required":["stage","text","missing"],"description":"The query's words do not find it."},{"type":"object","additionalProperties":false,"properties":{"position":{"type":"integer","format":"uint32","minimum":0,"description":"Its place among the hits, counted from 1."},"page":{"type":"integer","format":"uint32","minimum":0,"description":"The page it is on, at the request's page size."},"why":{"type":"array","items":{"$ref":"#/components/schemas/HitReason"},"description":"Why it sits there, as a hit on its page would say."},"below":{"description":"Why the last hit of the asked page sits above it, when it ranks after the page.","$ref":"#/components/schemas/HitComparison"},"stage":{"type":"string","const":"ranked"}},"required":["stage","position","page"],"description":"It is found, and ranks on another page: after this one, or before it on a later page."},{"type":"object","additionalProperties":false,"properties":{"reachable":{"type":"integer","format":"uint64","minimum":0,"description":"How many hits pages reach."},"total":{"type":"integer","format":"uint64","minimum":0,"description":"How many hits match."},"stage":{"type":"string","const":"unreachable"}},"required":["stage","reachable","total"],"description":"It is found, but more hits than pages reach rank above it, so its place is not known."},{"type":"object","additionalProperties":false,"properties":{"position":{"type":"integer","format":"uint32","minimum":0},"stage":{"type":"string","const":"here"}},"required":["stage","position"],"description":"It is on the page."}],"description":"The first stage that kept the entity off the page, with what it found, or the entity's place on it."},"Slug":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$","description":"An id unique within its file, such as a search test's, an overlay's or a pattern's."},"HeldWord":{"type":"object","additionalProperties":false,"properties":{"word":{"type":"string","description":"The word as the engine reads the query."},"field":{"$ref":"#/components/schemas/FieldPath","description":"The field, such as `attributes.mpn` or `brand`."},"holds":{"type":"string","description":"The field's word, as it writes it, such as `1K0615301`."},"why":{"$ref":"#/components/schemas/Unlooked"}},"required":["word","field","holds","why"],"description":"A word of the query that a field of the entity holds where the search does not look for it."},"Unlooked":{"oneOf":[{"type":"string","const":"not_searched","description":"The type does not search the field; it holds the word as typed."},{"type":"string","const":"exempt_from_typos","description":"The attribute is exempt from typos, and holds the word with as many typos as its length would allow elsewhere."}],"description":"Why the search does not find a word a field holds."},"NextStep":{"oneOf":[{"type":"object","properties":{"step":{"type":"string","const":"check_catalog"}},"required":["step"],"additionalProperties":false,"description":"Check the entity in the shop's export: the snapshot was read from it."},{"type":"object","additionalProperties":false,"properties":{"overlay":{"$ref":"#/components/schemas/Slug"},"step":{"type":"string","const":"open_overlay"}},"required":["step","overlay"],"description":"Open the overlay that may hide it."},{"type":"object","properties":{"step":{"type":"string","const":"search_words"}},"required":["step"],"additionalProperties":false,"description":"Search with words: other types show in sections of their own beside the listed one only then."},{"type":"object","additionalProperties":false,"properties":{"channel":{"$ref":"#/components/schemas/ChannelId"},"step":{"type":"string","const":"add_offer"}},"required":["step","channel"],"description":"Give it an offer in the channel, in the shop's export."},{"type":"object","additionalProperties":false,"properties":{"channel":{"$ref":"#/components/schemas/ChannelId"},"step":{"type":"string","const":"change_assortment"}},"required":["step","channel"],"description":"Change the channel's assortment in `channels.json`, or the product's data it reads in the shop's export."},{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"step":{"type":"string","const":"assign_category"}},"required":["step","category"],"description":"Assign it to the category, in the shop's export."},{"type":"object","additionalProperties":false,"properties":{"rule":{"$ref":"#/components/schemas/RuleId"},"description":{"type":"string"},"step":{"type":"string","const":"open_rule"}},"required":["step","rule","description"],"description":"Open the rule, to change its condition or what it keeps."},{"type":"object","additionalProperties":false,"properties":{"where":{"$ref":"#/components/schemas/Selector"},"source":{"$ref":"#/components/schemas/FilterSource"},"step":{"type":"string","const":"remove_filter"}},"required":["step","where","source"],"description":"Remove the filter, as the shopper's chip does."},{"type":"object","additionalProperties":false,"properties":{"words":{"type":"array","items":{"type":"string"}},"step":{"type":"string","const":"add_synonym"}},"required":["step","words"],"description":"Add a synonym, so these words find what it holds."},{"type":"object","additionalProperties":false,"properties":{"words":{"type":"array","items":{"type":"string"}},"step":{"type":"string","const":"add_keywords"}},"required":["step","words"],"description":"Add these words to its search words with an overlay."},{"type":"object","additionalProperties":false,"properties":{"position":{"type":"integer","format":"uint32","minimum":0},"step":{"type":"string","const":"pin"}},"required":["step","position"],"description":"Pin it with a rule at this place, so it shows on the page whatever ranks above it."},{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"step":{"type":"string","const":"open_search_settings"}},"required":["step","type"],"description":"Open how the type is searched: its fields in order, its typo settings and the attributes exempt from typos."}],"description":"One thing the merchant can do to show the entity on the page."},"CatalogUploaded":{"type":"object","properties":{"snapshot":{"$ref":"#/components/schemas/Snapshot"},"run":{"description":"The index run that makes it searchable; none before a release is published.","$ref":"#/components/schemas/IndexRun"},"next":{"type":"string","description":"What happens next, as a sentence."}},"required":["snapshot","next"],"description":"The answer to `PUT /api/v1/catalog`."},"Snapshot":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/SnapshotId"},"bytes":{"type":"integer","format":"uint64","minimum":0},"uploaded_at":{"$ref":"#/components/schemas/Timestamp","description":"The last time these bytes were sent. A publish or rebuild indexes the current catalog: the snapshot uploaded last\nthat is kept and that the indexer did not reject."},"removed_at":{"description":"When the indexer's clean-up removed its file because nothing needed it any more; its runs stay. Sending the same\nbytes again brings it back.","$ref":"#/components/schemas/Timestamp"}},"required":["id","bytes","uploaded_at"],"description":"A catalog as it was uploaded, named by its content."},"IndexRun":{"type":"object","properties":{"id":{"type":"integer","format":"uint64","minimum":0},"state":{"$ref":"#/components/schemas/RunState"},"trigger":{"description":"What queued the run; left out for runs queued before triggers were recorded. Its report's `course.cause` says\nsomething else: why the run took the way it took live.","$ref":"#/components/schemas/RunTrigger"},"snapshot_id":{"$ref":"#/components/schemas/SnapshotId"},"release_id":{"$ref":"#/components/schemas/ReleaseId"},"attempts":{"type":"integer","format":"uint32","minimum":0,"description":"How often an indexer took it up; a run whose indexer died is taken up again, three times at most."},"progress":{"description":"How far it got: while it runs, and for a failed run the step it failed in. Left out for any other run.","$ref":"#/components/schemas/RunProgress"},"report":{"description":"What the run did, once it finished.","$ref":"#/components/schemas/RunReport"},"error":{"type":"string","description":"Why it failed, as a sentence."},"created_at":{"$ref":"#/components/schemas/Timestamp"},"started_at":{"$ref":"#/components/schemas/Timestamp"},"finished_at":{"$ref":"#/components/schemas/Timestamp"},"live_at":{"description":"When its indexes became the ones searches read.","$ref":"#/components/schemas/Timestamp"},"live":{"type":"boolean","description":"Searches read this run now, the latest that succeeded. Left out for any other run. A run that changed nothing\nsays so in its report: its course is `unchanged`."},"snapshot":{"description":"Its snapshot, whose `removed_at` says whether its file is still kept.","$ref":"#/components/schemas/Snapshot"},"release":{"description":"On the live run: its release.","$ref":"#/components/schemas/StoredRelease"}},"required":["id","state","snapshot_id","release_id","attempts","created_at"],"description":"A snapshot and a release compiled into the engine; the latest that succeeded is live."},"RunState":{"oneOf":[{"type":"string","const":"queued","description":"Waiting for an indexer."},{"type":"string","const":"running","description":"An indexer is building the indexes."},{"type":"string","const":"succeeded","description":"The indexes are live: searches read this run's snapshot and release."},{"type":"string","const":"failed","description":"The run stopped; `error` says why. Nothing it built went live."},{"type":"string","const":"superseded","description":"A newer run replaced it: one was queued while it waited, or went live before it finished. Its indexes, if any,\nwere dropped."}],"description":"Where an index run stands."},"RunTrigger":{"oneOf":[{"type":"string","const":"upload","description":"A catalog sent to `PUT /api/v1/catalog`."},{"type":"string","const":"import","description":"An import published."},{"type":"string","const":"release","description":"A release published, or published again to roll back."},{"type":"string","const":"rebuild","description":"A rebuild asked for with `POST /api/v1/index-runs`."},{"type":"string","const":"refresh","description":"The indexer itself: a sale window opened or closed, an overlay ended, or another version of it built the live\nindexes."},{"type":"string","const":"fetch","description":"A fetch of the catalog source found its feed changed."}],"description":"What queued an index run."},"RunProgress":{"type":"object","additionalProperties":false,"properties":{"stage":{"$ref":"#/components/schemas/RunStage"},"entities":{"type":"integer","format":"uint64","minimum":0,"description":"Entities read and checked so far; from `indexing` on, all the run indexes."},"compiled":{"type":"integer","format":"uint64","minimum":0,"description":"Entities compiled and handed to the engine so far."},"indexed":{"type":"integer","format":"uint64","minimum":0,"description":"Documents the engine has indexed so far."}},"required":["stage","entities","compiled","indexed"],"description":"How far an index run got, written by the indexer while it works, so a merchant watches it go through. A failed run\nkeeps the last it wrote, so its report says the step it failed in."},"RunStage":{"oneOf":[{"type":"string","const":"reading","description":"Learning the file: its columns, its mapping and where each product ends."},{"type":"string","const":"checking","description":"Mapping every row and reading its values against the release; counts `entities`."},{"type":"string","const":"indexing","description":"Compiling the entities and writing them to new indexes beside the live ones; counts `compiled` and `indexed`."},{"type":"string","const":"going_live","description":"Swapping the new indexes in for the live ones."}],"description":"What a running index run does now, in the order it does it."},"RunReport":{"type":"object","additionalProperties":false,"properties":{"rows":{"type":"integer","format":"uint64","minimum":0,"description":"Rows read from the feed, blank lines not counted."},"feed":{"description":"How the export was read and mapped; left out for OrbSearch's own JSON Lines, which need no mapping.","$ref":"#/components/schemas/FeedReport"},"entities":{"type":"integer","format":"uint64","minimum":0,"description":"Entities indexed."},"documents":{"type":"integer","format":"uint64","minimum":0,"description":"Engine documents written, over every index."},"indexes":{"type":"array","items":{"type":"string"},"description":"The engine indexes the run made live, one per entity type and locale, such as `product-de`."},"rejected":{"type":"integer","format":"uint64","minimum":0,"description":"Rows left out because something in them is wrong; the rest of the feed was indexed."},"rejections":{"type":"array","items":{"$ref":"#/components/schemas/RejectionGroup"},"description":"The rejected rows grouped by what is wrong, the most common first, each with its first rows as examples."},"values":{"$ref":"#/components/schemas/ValuesReport","description":"What the import made of the attribute values: those it left out, and those whose meaning it derived."},"defaults":{"$ref":"#/components/schemas/Defaults","description":"The release files the release leaves out, derived from this run's catalog, each setting with its reason. The\nQuery API reads them with the release while the run is live."},"refresh_at":{"description":"The next moment a sale window opens or closes, or an overlay ends. The indexes hold the prices and corrections\nof the run's time, so the indexer builds the same snapshot and release again then.","$ref":"#/components/schemas/Timestamp"},"course":{"description":"How the run took its release live and why; left out for a run that failed or has not gone live yet. A run that\nswitched or only wrote settings carries the counts of the live run it followed, since its catalog is the same.","$ref":"#/components/schemas/Course"},"offers":{"description":"The offer changes the run's indexes hold: those received since its snapshot first arrived, applied on top of it\nwhen the run built its indexes, and every batch the indexer wrote into them while the run was live. Left out when\nthey hold none. A run that switched or only wrote settings carries the live run's, since its indexes are the\nsame.","$ref":"#/components/schemas/AppliedOffers"}},"required":["rows","entities","documents","indexes","rejected"],"description":"What an index run did with its snapshot: how much it indexed, and every row it left out and why."},"AppliedOffers":{"type":"object","additionalProperties":false,"properties":{"revision":{"type":"integer","format":"uint64","minimum":0,"description":"The latest batch the indexes hold, which every trace names."},"received_at":{"$ref":"#/components/schemas/Timestamp","description":"When that batch arrived: the prices and stock searches show are at least as new."},"products":{"type":"integer","format":"uint64","minimum":0,"description":"The products of the catalog whose offers the changes reach."}},"required":["revision","received_at","products"],"description":"The offer changes a run's indexes hold."},"StoredRelease":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/ReleaseId"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"published_at":{"description":"When it was last published; the release published last is the one index runs compile.","$ref":"#/components/schemas/Timestamp"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"On a single release: its files by name, in the canonical form the Query API wrote."},"role":{"description":"Where it stands among the releases index runs took live; left out for any other release, so the panel needs\nto work nothing out.","$ref":"#/components/schemas/ReleaseRole"},"course":{"description":"How the latest run that took it live did so; left out for a release that never went live.","$ref":"#/components/schemas/Course"}},"required":["id","created_at"],"description":"A release as the control plane stores it, with when it was published. Storing does not publish it."},"ReleaseRole":{"oneOf":[{"type":"string","const":"live","description":"Searches read it now."},{"type":"string","const":"previous","description":"The release the live one replaced, which publishing it again, a rollback, brings back."},{"type":"string","const":"going_live","description":"It is published and an index run is queued or running; it is not live yet."}],"description":"Where a release stands among those index runs took live."},"CatalogSource":{"type":"object","properties":{"url":{"type":"string"},"authentication":{"description":"How the fetch signs in, without its secret; left out when it does not.","$ref":"#/components/schemas/SourceAuthentication"},"interval_minutes":{"type":"integer","format":"uint32","minimum":0,"description":"How often the feed is fetched."},"on_change":{"$ref":"#/components/schemas/OnChange"},"next_fetch_at":{"$ref":"#/components/schemas/Timestamp","description":"When the schedule fetches next: at once after a fetch was asked for, otherwise one interval after the last one\nstarted."},"fetch_requested_at":{"description":"When a fetch was asked for that has not started yet.","$ref":"#/components/schemas/Timestamp"},"needs_attention":{"type":"boolean","description":"The latest changed feed is held back, or the last three fetches failed; Overview lists the source then. Left\nout otherwise."},"fetches":{"type":"array","items":{"$ref":"#/components/schemas/CatalogFetch"},"description":"The last ten fetches, newest first."}},"required":["url","interval_minutes","on_change","next_fetch_at","fetches"],"description":"The feed the catalog is fetched from on a schedule, with its latest fetches."},"SourceAuthentication":{"oneOf":[{"type":"object","properties":{"username":{"type":"string"},"kind":{"type":"string","const":"basic"}},"required":["kind","username"],"description":"HTTP basic authentication."},{"type":"object","properties":{"name":{"type":"string"},"kind":{"type":"string","const":"header"}},"required":["kind","name"],"description":"A header that carries a token, such as `Authorization` or `X-Api-Key`."}],"description":"How a catalog source signs in, as it is shown: its secret never is."},"OnChange":{"oneOf":[{"type":"string","const":"publish","description":"It goes live at once, unless it would remove half the live products or more."},{"type":"string","const":"draft","description":"It waits as a draft import to review and publish."}],"description":"What a fetch does with a feed that changed."},"CatalogFetch":{"type":"object","properties":{"id":{"type":"integer","format":"uint64","minimum":0},"requested":{"type":"boolean","description":"Someone asked for it; the schedule made the others. Left out for those."},"started_at":{"$ref":"#/components/schemas/Timestamp"},"finished_at":{"description":"Left out while it runs.","$ref":"#/components/schemas/Timestamp"},"result":{"description":"Left out while it runs.","$ref":"#/components/schemas/FetchResult"},"failure":{"description":"Why it failed.","$ref":"#/components/schemas/FetchFailure"},"status":{"type":"integer","format":"uint16","minimum":0,"maximum":65535,"description":"The feed server's HTTP status, where it answered with an error."},"message":{"type":"string","description":"What a failed or held-back fetch means for the merchant, as a sentence."},"bytes":{"type":"integer","format":"uint64","minimum":0,"description":"The size of the feed, where it arrived."},"import":{"description":"The import a changed feed became: its state says whether it went live, and once published, its run.","$ref":"#/components/schemas/Import"}},"required":["id","started_at"],"description":"One fetch of the catalog source and how it ended."},"FetchResult":{"oneOf":[{"type":"string","const":"unchanged","description":"The feed is the one fetched before, by the server's 304 or by its bytes, so nothing was queued."},{"type":"string","const":"published","description":"The feed changed and its import is on its way live."},{"type":"string","const":"drafted","description":"The feed changed and waits as a draft import."},{"type":"string","const":"held_back","description":"The feed would remove half the live products or more, so it waits as a draft import, to be published with\nthe number confirmed."},{"type":"string","const":"failed","description":"Nothing changed for searches; `failure` says why."}],"description":"How a fetch of the catalog source ended."},"FetchFailure":{"oneOf":[{"type":"string","const":"address_refused","description":"The host is in a private, loopback or link-local network, which the stack does not fetch from."},{"type":"string","const":"unreachable","description":"The host is unknown, or did not answer."},{"type":"string","const":"timed_out","description":"The feed did not arrive in time."},{"type":"string","const":"unauthorized","description":"The server answered 401 or 403: the credentials are wrong or missing."},{"type":"string","const":"http_error","description":"The server answered with another error status, in `status`."},{"type":"string","const":"too_many_redirects","description":"The URL redirects more than five times."},{"type":"string","const":"too_large","description":"The feed is larger than the largest upload, 2 GB."},{"type":"string","const":"empty","description":"The server sent nothing."},{"type":"string","const":"unreadable","description":"The Query API cannot read the file."},{"type":"string","const":"not_published","description":"The feed arrived, but publishing it was refused; the next fetch tries again."},{"type":"string","const":"interrupted","description":"The fetch stopped halfway, such as when the control plane restarted."}],"description":"Why a fetch of the catalog source failed. What is live stays live."},"Import":{"type":"object","properties":{"id":{"type":"integer","format":"uint64","minimum":0},"state":{"$ref":"#/components/schemas/ImportState"},"version":{"$ref":"#/components/schemas/DraftVersion","description":"The version a write to this draft names. It changes with every change of the files and with the release the\ndraft builds on."},"export":{"$ref":"#/components/schemas/ImportExport"},"base_release_id":{"description":"The release the import was built on: for a draft, the one published when its export arrived, though a draft\nalways builds on the release published now, so publishing it never undoes a release published since; once\npublished, the one it went live on. Left out before any release was published.","$ref":"#/components/schemas/ReleaseId"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"The release files the import reads its export with instead of the base release's, by name, in the canonical form\nthe Query API wrote; left out when it changes none."},"release_id":{"description":"Once published: the release its files made.","$ref":"#/components/schemas/ReleaseId"},"run":{"description":"Once published: the index run that makes it live, the latest if it was published more than once.","$ref":"#/components/schemas/IndexRun"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"published_at":{"description":"When it was last published.","$ref":"#/components/schemas/Timestamp"},"discarded_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["id","state","version","export","created_at"],"description":"An export on its way to live: the file as the shop wrote it, the release files it is read with, and once published,\nthe release and the index run that make it live. Imports are never deleted, so every export that went live can go\nlive again while its file is kept."},"ImportState":{"oneOf":[{"type":"string","const":"draft","description":"Kept, not published: it can be inspected, corrected, published or discarded."},{"type":"string","const":"going_live","description":"Published; its index run is queued or running."},{"type":"string","const":"live","description":"Searches read it."},{"type":"string","const":"replaced","description":"It was live, or on its way, until a later publish took its place; publishing it again goes back to it."},{"type":"string","const":"failed","description":"Its index run failed, and `run.error` says why. What was live before stays live."},{"type":"string","const":"discarded","description":"Put aside without going live."}],"description":"Where an import stands."},"DraftVersion":{"type":"string","pattern":"^\\S{1,128}$","description":"The version of a draft: it changes with every change of the draft's release files, and with the release the draft builds on. A write names the version it read, and is refused when the draft has moved on. Treat it as opaque."},"ImportExport":{"type":"object","properties":{"name":{"type":"string","description":"The file's name, as the panel or the `name` parameter gave it."},"snapshot_id":{"$ref":"#/components/schemas/SnapshotId"},"bytes":{"type":"integer","format":"uint64","minimum":0},"removed_at":{"description":"When the indexer's clean-up removed the file. The import can no longer be inspected or published again until\nthe same export is sent again.","$ref":"#/components/schemas/Timestamp"}},"required":["snapshot_id","bytes"],"description":"The export of an import: the file's name as it arrived and the snapshot that keeps its bytes."},"CatalogSourceChange":{"type":"object","properties":{"url":{"type":"string","description":"An `http` or `https` URL, without a user or password in it."},"interval_minutes":{"type":"integer","format":"uint32","minimum":5,"description":"How often to fetch it. Default: 60.","maximum":1440},"on_change":{"description":"What a feed that changed does. Default: `publish`.","$ref":"#/components/schemas/OnChange"},"authentication":{"oneOf":[{"$ref":"#/components/schemas/FeedCredentials"},{"type":"null"}],"description":"How to sign in, kept encrypted and sent only to the feed's own host. Left out, the kept credentials stay;\n`null` removes them."}},"required":["url"],"description":"The feed to fetch the catalog from, and how."},"FeedCredentials":{"oneOf":[{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"},"kind":{"type":"string","const":"basic"}},"required":["kind","username","password"],"description":"HTTP basic authentication."},{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"},"kind":{"type":"string","const":"header"}},"required":["kind","name","value"],"description":"A header that carries a token. Its name holds letters, digits and hyphens."}],"description":"The credentials a feed's server asks for."},"Imports":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Import"}},"links":{"$ref":"#/components/schemas/PageLinks"},"meta":{"$ref":"#/components/schemas/PageMeta"}},"required":["data","links","meta"],"description":"A page of imports, newest first."},"PageLinks":{"type":"object","properties":{"first":{"type":"string"},"last":{"type":"string"},"prev":{"type":"string","description":"Left out on the first page."},"next":{"type":"string","description":"Left out on the last page."}},"required":["first","last"],"description":"The pages around this one, as URLs to fetch."},"PageMeta":{"type":"object","properties":{"current_page":{"type":"integer","format":"uint32","minimum":0},"last_page":{"type":"integer","format":"uint32","minimum":0},"per_page":{"type":"integer","format":"uint32","minimum":0},"total":{"type":"integer","format":"uint64","minimum":0}},"required":["current_page","last_page","per_page","total"],"description":"Where this page stands in the whole list."},"ImportStarted":{"type":"object","properties":{"import":{"$ref":"#/components/schemas/Import"},"next":{"type":"string","description":"What to do next, as a sentence."}},"required":["import","next"],"description":"The answer to `POST /api/v1/imports`."},"ImportChange":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion","description":"The import's `version` as read; a draft that changed since refuses the write."},"files":{"type":"object","additionalProperties":true}},"required":["version","files"],"description":"Release files to read an import with, such as `{ \"files\": { \"feed.json\": { \"columns\": { \"Preis\": \"price\" } } } }`.\nA file given as `null` is dropped, so the base release's applies again."},"DraftConflict":{"type":"object","description":"A write to a draft that was refused with 409: a problem, and where the draft stands.","properties":{"type":{"type":"string","description":"A URI naming the kind of problem."},"title":{"type":"string","description":"The status's own phrase, such as `Bad Request`."},"status":{"type":"integer","format":"uint16","minimum":0,"maximum":65535,"description":"The answer's HTTP status."},"detail":{"type":"string","description":"What happened and what to do next, for the person who can fix it."},"violations":{"type":"array","items":{"$ref":"#/components/schemas/Violation"},"description":"For invalid input, every value that is wrong."},"draft":{"description":"The draft as it is now, with the version to write on and its files, so the write can be made again on top of\nthe change. Left out where the draft was published or discarded.","$ref":"#/components/schemas/Import"}},"required":["type","title","status"]},"ImportPublished":{"type":"object","properties":{"import":{"$ref":"#/components/schemas/Import"},"next":{"type":"string","description":"What happens next, as a sentence."}},"required":["import","next"],"description":"The answer to `POST /api/v1/imports/{import}/publish`."},"DraftRules":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion","description":"The draft's version this answer is of; the next write names it."},"rules":{"$ref":"#/components/schemas/RuleList"},"rule":{"description":"After a write: the rule created, changed or moved; left out after a delete and in a list.","$ref":"#/components/schemas/RuleId"}},"required":["version","rules"],"description":"A draft's rules and the version to write on."},"CreateRule":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion"},"id":{"description":"The rule's id. Default: made from the name, and made unique.","$ref":"#/components/schemas/RuleId"},"name":{"type":"string","description":"The rule's name, one line up to 200 characters. Default: what the rule does, in a sentence."},"enabled":{"type":"boolean","description":"Default: on."},"position":{"type":"integer","format":"uint32","minimum":0,"description":"Its place in the order, counted from 1; a number past the end puts it last. Default: first, since a merchant's\nnew rule outranks the older ones."},"fields":{"$ref":"#/components/schemas/RuleFields"}},"required":["version","fields"],"description":"A rule to add and the draft version it is written on."},"ChangeRule":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion"},"name":{"type":"string"},"enabled":{"type":"boolean"},"fields":{"description":"The new fields, all of them: the rule's condition, effects and window are replaced. Refused for a rule the\nfields cannot express, since the change would drop what they leave out.","$ref":"#/components/schemas/RuleFields"}},"required":["version"],"description":"A change of one rule and the draft version it is written on."},"MoveRule":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion"},"position":{"type":"integer","format":"uint32","minimum":1,"description":"Its new place, counted from 1; a number past the end puts it last."}},"required":["version","position"],"description":"A move of one rule and the draft version it is written on."},"DraftSynonyms":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion","description":"The draft's version this answer is of; the next write names it."},"synonyms":{"$ref":"#/components/schemas/SynonymList"},"written":{"type":"array","items":{"$ref":"#/components/schemas/SynonymId"},"description":"After a write: the entries added or changed, in the order sent."},"refused":{"type":"array","items":{"$ref":"#/components/schemas/RefusedSynonym"},"description":"After an add: the entries that clash and were not written, each with why."}},"required":["version","synonyms"],"description":"A draft's synonyms and the version to write on."},"AddSynonyms":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion"},"locale":{"$ref":"#/components/schemas/Locale","description":"The language, such as `de`: the first of the list's `locales` is the shop's."},"entries":{"type":"array","items":{"$ref":"#/components/schemas/SynonymEntry"},"minItems":1,"maxItems":1000},"despite_never":{"type":"boolean","description":"Writes an entry that merges words a `never` entry keeps apart, which is refused otherwise."}},"required":["version","locale","entries"],"description":"Synonym entries to add to one language of a draft, and the version they are written on."},"ChangeSynonym":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion"},"entry":{"$ref":"#/components/schemas/SynonymEntry"},"despite_never":{"type":"boolean","description":"Writes an entry that merges words a `never` entry keeps apart, which is refused otherwise."}},"required":["version","entry"],"description":"An entry's new words and the draft version they are written on."},"SynonymToCheck":{"type":"object","properties":{"locale":{"$ref":"#/components/schemas/Locale"},"channel":{"description":"The channel the words are searched in. Default: the first channel that serves a locale reading this language's\nsynonyms, such as `de-CH` for `de`.","$ref":"#/components/schemas/ChannelId"},"entry":{"$ref":"#/components/schemas/SynonymEntry"},"id":{"description":"The entry being changed, whose own words clash with nothing.","$ref":"#/components/schemas/SynonymId"}},"required":["locale","entry"],"description":"An entry being typed, to check before it is written."},"DraftSearchSettings":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion","description":"The draft's version this answer is of; the next write names it."},"settings":{"$ref":"#/components/schemas/SearchSettings"}},"required":["version","settings"],"description":"A draft's search settings and the version to write on."},"ChangeSearch":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/DraftVersion"},"fields":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/FieldPath"}},{"type":"null"}],"description":"The searched fields in priority order, all of them, at least one."},"typos":{"oneOf":[{"$ref":"#/components/schemas/TypoTolerance"},{"type":"null"}],"description":"Whether and from how many letters on words find with a typo."},"exempt":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/AttributeCode"}},{"type":"null"}],"description":"The attributes exempt from typos, all of them; `[]` exempts none."}},"required":["version"],"description":"A change of how one type is searched and the draft version it is written on."},"Releases":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/StoredRelease"}},"links":{"$ref":"#/components/schemas/PageLinks"},"meta":{"$ref":"#/components/schemas/PageMeta"}},"required":["data","links","meta"],"description":"A page of releases, newest first."},"NewRelease":{"type":"object","properties":{"files":{"type":"object","additionalProperties":true}},"required":["files"],"description":"A release to store: its files by name, each as its JSON object or as text, such as\n`{ \"files\": { \"channels.json\": { \"channels\": [...] } } }`."},"ReleasePublished":{"type":"object","properties":{"release":{"$ref":"#/components/schemas/StoredRelease"},"run":{"description":"The index run that makes it live; none before a catalog is uploaded.","$ref":"#/components/schemas/IndexRun"},"next":{"type":"string","description":"What happens next, as a sentence."}},"required":["release","next"],"description":"The answer to `POST /api/v1/releases/{id}/publish`."},"IndexRuns":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IndexRun"}},"links":{"$ref":"#/components/schemas/PageLinks"},"meta":{"$ref":"#/components/schemas/PageMeta"}},"required":["data","links","meta"],"description":"A page of index runs, newest first."},"OffersChanged":{"type":"object","properties":{"revision":{"type":"integer","format":"uint64","minimum":0,"description":"The revision the kept changes make; left out when none was kept."},"rows":{"type":"array","items":{"$ref":"#/components/schemas/OfferVerdict"},"description":"One verdict per change, in the order sent."},"next":{"type":"string","description":"What happens next, as a sentence."}},"required":["rows","next"],"description":"The answer to `PUT /api/v1/offers`."},"Storage":{"type":"object","properties":{"kept_snapshots":{"type":"integer","format":"uint32","minimum":0,"description":"How many snapshots that went live are kept beside the live one, newest first. Default: 5."},"snapshots":{"$ref":"#/components/schemas/StoredFiles","description":"The snapshot files in the storage."},"engine":{"description":"The engine's indexes on disk, as the indexer measured them after it last cleaned up; left out before it did.","$ref":"#/components/schemas/EngineSize"}},"required":["kept_snapshots","snapshots"],"description":"What the storage keeps and the disk it takes."},"StoredFiles":{"type":"object","properties":{"count":{"type":"integer","format":"uint64","minimum":0},"bytes":{"type":"integer","format":"uint64","minimum":0}},"required":["count","bytes"],"description":"Files and the bytes they take together."},"EngineSize":{"type":"object","properties":{"bytes":{"type":"integer","format":"uint64","minimum":0},"measured_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["bytes","measured_at"],"description":"The engine's size on disk, when it was measured."},"Day":{"type":"string","pattern":"^[0-9]{4}-[0-9]{2}-[0-9]{2}$","description":"A UTC day, such as `2026-10-09`."},"Insights":{"type":"object","additionalProperties":false,"properties":{"from":{"$ref":"#/components/schemas/Day"},"to":{"$ref":"#/components/schemas/Day"},"traffic":{"$ref":"#/components/schemas/Traffic"},"rolled_up_at":{"description":"When the log was last rolled up, about once a minute; a search shows at the next roll-up. Left out until the\nfirst.","$ref":"#/components/schemas/Timestamp"},"releases":{"type":"array","items":{"$ref":"#/components/schemas/ReleaseId"},"description":"The releases that answered the searches counted, the latest first."},"searches":{"type":"integer","format":"uint64","minimum":0},"no_results":{"$ref":"#/components/schemas/Share","description":"The searches that found nothing: no section held a result and nothing redirected."},"click_through":{"$ref":"#/components/schemas/Share","description":"The searches with a click, of all searches."},"no_click":{"$ref":"#/components/schemas/Share","description":"The searches with results and no click, of the searches with results."},"average_position":{"type":"number","format":"double","description":"Where the clicked hits of the listed section stood, on average, counted from 1 across the pages; left out\nwithout such a click."},"category_pages":{"type":"integer","format":"uint64","minimum":0,"description":"First pages of category listings."},"autocomplete":{"type":"integer","format":"uint64","minimum":0,"description":"Answers to a search box while the shopper typed."},"distinct_queries":{"type":"integer","format":"uint64","minimum":0,"description":"The queries counted in `queries`, over every day."},"days":{"type":"array","items":{"$ref":"#/components/schemas/InsightsDay"},"description":"Every day of the range, one without searches included."},"queries":{"type":"array","items":{"$ref":"#/components/schemas/QueryInsight"},"description":"The queries searched most, by their words as the engine reads them. A query is counted on a day once two page\nviews typed it that day."},"no_result_queries":{"type":"array","items":{"$ref":"#/components/schemas/QueryInsight"},"description":"The queries that found nothing most often, the same way counted."},"no_click_queries":{"type":"array","items":{"$ref":"#/components/schemas/QueryInsight"},"description":"The queries whose searches had results and drew no click most often, the same way counted."},"pages":{"type":"array","items":{"$ref":"#/components/schemas/PageInsight"},"description":"The pages searched most, the search page among them, each with the filters chosen on it."},"left_out":{"$ref":"#/components/schemas/LeftOut"}},"required":["from","to","traffic","releases","searches","no_results","click_through","no_click","category_pages","autocomplete","distinct_queries","days","queries","no_result_queries","no_click_queries","pages","left_out"],"description":"What shoppers searched between two UTC days, both included, from the daily rollups. A search is the first page of\nan answer on the search page with words typed: the pages after it, category pages and autocomplete are counted\napart, and the search page without words, which a storefront asks for its own rails, is no search. A search has\nresults when it found something and did not send the shopper elsewhere, so a shopper could click one; its clicks\nare those that name its query ID within `LIMITS.event_hours`, a search's day counting them until then."},"Share":{"type":"object","additionalProperties":false,"properties":{"count":{"type":"integer","format":"uint64","minimum":0},"of":{"type":"integer","format":"uint64","minimum":0}},"required":["count","of"],"description":"A count and what it is a share of, such as 12 searches that found nothing of 140."},"InsightsDay":{"type":"object","additionalProperties":false,"properties":{"day":{"$ref":"#/components/schemas/Day"},"searches":{"type":"integer","format":"uint64","minimum":0},"no_results":{"type":"integer","format":"uint64","minimum":0,"description":"Of `searches`."},"clicked":{"type":"integer","format":"uint64","minimum":0,"description":"Of `searches`, those with a click."},"category_pages":{"type":"integer","format":"uint64","minimum":0},"autocomplete":{"type":"integer","format":"uint64","minimum":0},"took_ms":{"description":"How long the Query API took for the day's answers on the search page, the pages after the first included; left\nout on a day without any.","$ref":"#/components/schemas/Percentiles"}},"required":["day","searches","no_results","clicked","category_pages","autocomplete"]},"Percentiles":{"type":"object","additionalProperties":false,"properties":{"p50":{"type":"number","format":"double"},"p95":{"type":"number","format":"double"},"p99":{"type":"number","format":"double"}},"required":["p50","p95","p99"]},"QueryInsight":{"type":"object","additionalProperties":false,"properties":{"query":{"type":"string","description":"The query's words as the engine reads them, such as `sofa grau`."},"searches":{"type":"integer","format":"uint64","minimum":0},"no_results":{"type":"integer","format":"uint64","minimum":0,"description":"Of `searches`."},"clicked":{"type":"integer","format":"uint64","minimum":0,"description":"Of `searches`, those with a click."},"with_results":{"type":"integer","format":"uint64","minimum":0,"description":"Of `searches`, those with results."},"no_click":{"type":"integer","format":"uint64","minimum":0,"description":"Of `with_results`, those without a click."},"average_position":{"type":"number","format":"double","description":"Where its clicked hits of the listed section stood, on average, counted from 1 across the pages; left out\nwithout such a click."},"page_views":{"type":"integer","format":"uint64","minimum":0,"description":"The page views that searched it, summed over the days."},"results":{"type":"integer","format":"uint64","minimum":0,"description":"What its latest search listed."}},"required":["query","searches","no_results","clicked","with_results","no_click","page_views","results"]},"PageInsight":{"type":"object","additionalProperties":false,"properties":{"category":{"description":"The category page's category; left out for the search page.","$ref":"#/components/schemas/CategoryId"},"searches":{"type":"integer","format":"uint64","minimum":0,"description":"First pages of its results."},"filtered":{"type":"integer","format":"uint64","minimum":0,"description":"Of `searches`, those with a filter chosen."},"filters":{"type":"array","items":{"$ref":"#/components/schemas/FilterInsight"},"description":"The filters chosen, the most chosen first: each with the searches that chose it, of `searches`."}},"required":["searches","filtered","filters"]},"FilterInsight":{"type":"object","additionalProperties":false,"properties":{"field":{"$ref":"#/components/schemas/FieldCode","description":"The facet's field, or `category` for the category facet."},"searches":{"type":"integer","format":"uint64","minimum":0}},"required":["field","searches"]},"LeftOut":{"type":"object","additionalProperties":false,"properties":{"traffic":{"type":"object","additionalProperties":{"type":"integer","format":"uint64","minimum":0},"description":"The first pages answered to every other kind of traffic, on every surface."},"rare_queries":{"type":"integer","format":"uint64","minimum":0,"description":"Searches whose query fewer than two page views typed that day, or that is longer than 200 characters: counted\nin `searches`, never shown as a query."},"personal_data":{"type":"integer","format":"uint64","minimum":0,"description":"Searches whose query held an email address or a phone number, which the log masked: counted in `searches`,\nnever shown as a query."},"not_recorded":{"type":"integer","format":"uint64","minimum":0,"description":"Searches of any traffic the Query API answered but could not log, since its buffer was full or Postgres did not\ntake them."},"events":{"$ref":"#/components/schemas/EventsLeftOut"}},"required":["traffic","rare_queries","personal_data","not_recorded","events"],"description":"How many each rule of the log left out of this report."},"EventsLeftOut":{"type":"object","additionalProperties":false,"properties":{"late":{"type":"integer","format":"uint64","minimum":0,"description":"Events of any traffic that arrived on one of the days more than `LIMITS.event_hours` after their answer, which\nthe Query API refused."},"unmatched":{"type":"integer","format":"uint64","minimum":0,"description":"Events of this traffic for an answer on one of the days that the log holds nothing of this traffic for: a\npreview's, one the log could not record, another traffic's, or a made-up query ID."},"not_recorded":{"type":"integer","format":"uint64","minimum":0,"description":"Events of any traffic the Query API took on one of the days but could not log, since its buffer was full or\nPostgres did not take them."}},"required":["late","unmatched","not_recorded"],"description":"The clicks and views no search of the report counts."},"CampaignReport":{"type":"object","additionalProperties":false,"properties":{"from":{"$ref":"#/components/schemas/Day"},"to":{"$ref":"#/components/schemas/Day"},"traffic":{"$ref":"#/components/schemas/Traffic"},"rolled_up_at":{"description":"When the log was last rolled up, about once a minute. Left out until the first.","$ref":"#/components/schemas/Timestamp"},"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/CampaignInsight"},"description":"Every campaign served or seen in the range, the most viewed first."}},"required":["from","to","traffic","campaigns"],"description":"The sponsored products' campaigns between two UTC days, both included, from the daily rollups, which outlive the 60\ndays of raw searches and events. A campaign is counted on the day of the answer that served it, its views and clicks\nwith it: those of the same traffic within `LIMITS.event_hours` that name a product with the campaign an answer of\ntheir page placed, by that answer's query ID or, for a later page, the first page's query ID and the page view."},"CampaignInsight":{"type":"object","additionalProperties":false,"properties":{"campaign":{"$ref":"#/components/schemas/Campaign"},"served":{"type":"integer","format":"uint64","minimum":0,"description":"The answers that placed one of its products, every page counted."},"views":{"type":"integer","format":"uint64","minimum":0,"description":"Its products seen: each once per query ID, once half of its tile was visible for a second or it was clicked."},"click_through":{"$ref":"#/components/schemas/Share","description":"The views clicked, of `views`."},"days":{"type":"array","items":{"$ref":"#/components/schemas/CampaignDay"},"description":"The days of the range it was served or seen on, the earliest first."}},"required":["campaign","served","views","click_through","days"]},"CampaignDay":{"type":"object","additionalProperties":false,"properties":{"day":{"$ref":"#/components/schemas/Day"},"served":{"type":"integer","format":"uint64","minimum":0},"views":{"type":"integer","format":"uint64","minimum":0},"clicks":{"type":"integer","format":"uint64","minimum":0,"description":"Of `views`, those clicked."}},"required":["day","served","views","clicks"]},"StorageChange":{"type":"object","properties":{"kept_snapshots":{"type":"integer","format":"uint32","minimum":0,"description":"How many snapshots that went live to keep beside the live one, newest first.","maximum":100}},"required":["kept_snapshots"],"description":"A change of what the storage keeps."},"Release":{"type":"object","additionalProperties":false,"properties":{"types":{"$ref":"#/components/schemas/Types","default":{}},"attributes":{"$ref":"#/components/schemas/Attributes","default":{}},"relations":{"$ref":"#/components/schemas/Relations","default":{}},"channels":{"$ref":"#/components/schemas/Channels","default":{}},"categories":{"$ref":"#/components/schemas/Categories","default":{}},"overlays":{"$ref":"#/components/schemas/Overlays","default":{}},"synonyms":{"$ref":"#/components/schemas/Synonyms","default":{}},"ranking":{"$ref":"#/components/schemas/Ranking","default":{}},"rules":{"$ref":"#/components/schemas/Rules","default":{}},"tests":{"$ref":"#/components/schemas/Tests","default":{}},"patterns":{"$ref":"#/components/schemas/Patterns","default":{}},"feed":{"$ref":"#/components/schemas/Feed","default":{}},"routes":{"$ref":"#/components/schemas/Routes","default":{}},"redirects":{"$ref":"#/components/schemas/Redirects","default":{}}},"description":"Every file of a release. A file left out of the folder is empty."},"Relations":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"relations":{"type":"array","items":{"$ref":"#/components/schemas/RelationDefinition"}}},"description":"`relations.json`: what links between entities mean."},"RelationDefinition":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/RelationCode"},"from":{"$ref":"#/components/schemas/TypeCode"},"to":{"$ref":"#/components/schemas/TypeCode"},"label":{"$ref":"#/components/schemas/Localized_string"},"input":{"$ref":"#/components/schemas/RelationInput","description":"How links arrive: inline on the entity, or as a stream of their own. Default: `inline`."},"qualifiers":{"type":"array","items":{"$ref":"#/components/schemas/Qualifier"},"description":"Notes that belong to one link, such as \"only with sport suspension\". Shown and traced, never filtered silently."},"create_missing":{"type":"boolean","description":"A link to a target that does not exist creates it, as a feed's brand name creates its brand."},"aliases":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/EntityId"},"description":"Spellings OrbSearch maps onto a target's id, such as `\"Robert Bosch GmbH\": \"bosch\"`. They survive every feed."},"search_target_title":{"type":"boolean","description":"Whether the target's title becomes searchable text of the source. Forty printer names may; a thousand vehicle\nnames may not. Default: `false`."}},"required":["code","from","to"],"description":"A link from entities of one type to entities of another, such as `brand` from `product` to `brand`."},"RelationCode":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"The code of a relation definition, such as `brand` or `compatible_with`."},"RelationInput":{"type":"string","enum":["inline","stream"]},"Qualifier":{"type":"object","additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/RelationCode"},"label":{"$ref":"#/components/schemas/Localized_string"}},"required":["code"],"description":"A note a single link may carry."},"Overlays":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"overlays":{"type":"array","items":{"$ref":"#/components/schemas/Overlay"}}},"description":"`overlays.json`: corrections to catalog data, applied when documents are built."},"Overlay":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/Slug"},"target":{"$ref":"#/components/schemas/OverlayTarget"},"hide":{"type":"boolean","description":"Hide the targets from every request. Hiding for some requests only is a rule."},"keywords":{"description":"Search terms added to the merchant's own keywords.","$ref":"#/components/schemas/Localized_Array_of_string"},"badges":{"description":"Badges shown on the targets' results, such as \"OE quality\".","$ref":"#/components/schemas/Localized_Array_of_string"},"fill":{"description":"Values set only where the catalog has none, so the correction steps aside once the feed is fixed.","$ref":"#/components/schemas/Patch"},"set":{"description":"Values that replace the catalog's; the panel shows them with a warning.","$ref":"#/components/schemas/Patch"},"until":{"description":"The overlay ends at this time.","$ref":"#/components/schemas/Timestamp"},"note":{"type":"string"},"origin":{"$ref":"#/components/schemas/Origin"}},"required":["id","target"],"description":"A correction keyed by an entity id or a selector, never by a snapshot row. An overlay whose target has left the\ncatalog stays, dormant, until it returns."},"OverlayTarget":{"type":"object","additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/TypeCode"},"id":{"$ref":"#/components/schemas/EntityId"},"where":{"$ref":"#/components/schemas/Selector"}},"required":["type"],"description":"The entities an overlay corrects: one by its id, or all that match a selector."},"Patch":{"type":"object","additionalProperties":false,"properties":{"attributes":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":true}}},"required":["attributes"],"description":"Attribute values an overlay writes, as a source would send them."},"Synonyms":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"synonyms":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$":{"$ref":"#/components/schemas/Dictionary"}},"description":"One dictionary per language, such as `de` or `en`."}},"description":"`synonyms.json`: a synonym dictionary per language."},"Dictionary":{"type":"object","additionalProperties":false,"properties":{"groups":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Words that mean the same, each way round, such as `[\"sofa\", \"couch\"]`."},"one_way":{"type":"array","items":{"$ref":"#/components/schemas/OneWay"},"description":"A word that also finds others, but not the other way round: `läufer` finds `teppich`."},"never":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Words that look alike but must never be merged, such as `[\"sessel\", \"sofa\"]`. The panel warns when a merchant\ntries."}},"description":"The synonyms of one language."},"OneWay":{"type":"object","additionalProperties":false,"properties":{"from":{"type":"string","description":"The word a shopper types."},"to":{"$ref":"#/components/schemas/OneOrMany_string","description":"The word or words it also finds."}},"required":["from","to"]},"Tests":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"tests":{"type":"array","items":{"$ref":"#/components/schemas/SearchTest"}}},"description":"`tests.json`: search tests, checked before every publish."},"SearchTest":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/Slug"},"description":{"type":"string"},"request":{"$ref":"#/components/schemas/SearchRequest","description":"The request, exactly as the Query API receives it. A fixed `time` makes scheduled rules testable."},"expect":{"$ref":"#/components/schemas/Expectations"}},"required":["id","request","expect"],"description":"A request and its expectations. Plan expectations (`redirect`, `resolved`, `rules`, `sections`) need no engine and\nrun against every draft in milliseconds; result expectations run against the index, also after index runs."},"Expectations":{"type":"object","additionalProperties":false,"properties":{"redirect":{"description":"The request redirects here.","$ref":"#/components/schemas/Redirect"},"resolved":{"description":"The resolution contains at least this.","$ref":"#/components/schemas/ResolvedExpectation"},"rules":{"$ref":"#/components/schemas/RuleExpectations"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/TypeCode"},"description":"The section order begins with these types."},"results":{"$ref":"#/components/schemas/ResultExpectations"}}},"ResolvedExpectation":{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/CategoryId"},"filters":{"description":"Detected filters, such as `{ \"color\": \"grey\" }`. Further detected filters do not fail the test.","$ref":"#/components/schemas/Selector"},"text":{"type":"string","description":"The text left after resolution."},"complete":{"type":"boolean"}}},"RuleExpectations":{"type":"object","additionalProperties":false,"properties":{"applied":{"type":"array","items":{"$ref":"#/components/schemas/RuleId"}},"not_applied":{"type":"array","items":{"$ref":"#/components/schemas/RuleId"}}}},"ResultExpectations":{"type":"object","additionalProperties":false,"properties":{"top":{"type":"integer","format":"uint32","minimum":0,"description":"How many hits `contains`, `excludes`, `all` and `none` look at. Default: 10."},"first":{"$ref":"#/components/schemas/EntityRef"},"contains":{"type":"array","items":{"$ref":"#/components/schemas/EntityRef"}},"excludes":{"type":"array","items":{"$ref":"#/components/schemas/EntityRef"}},"all":{"description":"Every hit within `top` matches this.","$ref":"#/components/schemas/Selector"},"none":{"description":"No hit within `top` matches this.","$ref":"#/components/schemas/Selector"}},"description":"Expectations on the hits of the first section of the entity's type."},"Patterns":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"patterns":{"type":"array","items":{"$ref":"#/components/schemas/Pattern"}},"built_in":{"$ref":"#/components/schemas/BuiltInPatterns","description":"The built-in patterns, each on unless switched off here."}},"description":"`patterns.json`: query patterns that become filters."},"Pattern":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/Slug"},"description":{"type":"string"},"phrases":{"$ref":"#/components/schemas/Localized_Array_of_string","description":"The phrases, matched like rule conditions after normalization. A plain list holds in every language."},"captures":{"$ref":"#/components/schemas/Capture","description":"How a captured number filters. Default: `exact`."},"filter":{"description":"Filters the pattern adds besides its captures, such as `{ \"shape\": \"round\" }`.","$ref":"#/components/schemas/Selector"}},"required":["id","phrases"],"description":"Phrases that, found in a query, become filters. A placeholder captures a number for a numeric attribute, in the\nattribute's unit, or for a numeric built-in field, `price`, `delivery_days` or `rating`: `{width} x {depth}` turns\n\"160 x 230\" into width 160 and depth 230, and \"lieferung in {delivery_days} tagen\" read `at_most` a delivery time.\nFor an option attribute it reads one of the values the attribute lists, by its code, a label or an alias, always\nexactly: `{load_index}{speed_index}` turns \"91V\" and \"91 V\" into load index 91 and speed index V. A placeholder\nwritten against a word or another placeholder may have a space between them, so `R{rim}` reads \"R16\" and \"R 16\"."},"Capture":{"oneOf":[{"type":"string","enum":["exact","at_most","at_least"]},{"type":"object","properties":{"within":{"type":"number"}},"required":["within"],"additionalProperties":false}],"description":"How a captured number filters: `\"exact\"`, `\"at_most\"`, `\"at_least\"`, or `{ \"within\": 5 }` for a tolerance either\nway."},"BuiltInPatterns":{"type":"object","additionalProperties":false,"properties":{"quantity":{"type":"boolean","description":"A number with a unit, such as \"bis 220 cm\", filters the one attribute that measures it. Default: on."},"price":{"type":"boolean","description":"A price in the channel's currency, such as \"unter 200 €\", filters the price. Default: on."}},"description":"The built-in patterns a merchant can switch off. Both are on by default."},"Routes":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"landing":{"$ref":"#/components/schemas/Landing","description":"Where a search lands when its query names a page and nothing is left as text, such as \"graues sofa\".\nDefault: `category`."},"search":{"description":"The search page's path, per locale where it differs, such as `/suche` or `{ \"de\": \"/suche\", \"en\": \"/search\" }`.\nDefault: `/search`.","$ref":"#/components/schemas/Localized_string"},"filter_pages":{"description":"The filter states that get a path of their own, such as `/wohnen/sofas/farbe--grau/`: a category with one value\nof each facet of a listed combination. Every other filter state is the category with parameters. Default: none.","$ref":"#/components/schemas/FilterPages"},"min_products":{"type":"integer","format":"uint32","minimum":1,"description":"The fewest products a filter page shows to be indexed; with fewer it is `noindex`. Default: 4."},"trailing_slash":{"$ref":"#/components/schemas/TrailingSlash","description":"Whether a page's path ends in a slash. Default: `as_written`, each path as the catalog writes it."},"parameters":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"$ref":"#/components/schemas/UrlParameter"}},"description":"The query parameters whose names differ from their codes, by code: a facet's field, such as `color`, or one of\nthe reserved `q`, `page`, `sort`, `from`, `ignore` and `redirect`. Every other parameter is named by its code;\na range is the name with `.gte` and `.lte`, such as `price.gte`.\n`{ \"color\": \"farbe\", \"q\": { \"name\": \"suche\", \"aliases\": [\"query\"] } }`"}},"description":"`routes.json`: the merchant's URLs."},"Landing":{"oneOf":[{"type":"string","const":"category","description":"On the merchant's page for what the query named: the category's page with the query's filters chosen, or the\nbrand's page when it names only a brand. The page brings its merchandising and its URL."},{"type":"string","const":"search","description":"On the search page, with the category and the filters as chips."}]},"FilterPages":{"type":"object","additionalProperties":false,"properties":{"facets":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"},"description":"The facets a path may hold, in the order of their segments: options, their groups, and the brand.\n`[\"brand\", \"gender\", \"material\"]`"},"combinations":{"type":"array","items":{"type":"array","items":{"$ref":"#/components/schemas/FieldCode"}},"description":"The combinations that get a path, each one to three of `facets`, with one value of each.\n`[[\"brand\"], [\"gender\"], [\"brand\", \"gender\"]]`"},"before_category":{"type":"object","additionalProperties":false,"patternProperties":{"^[a-z][a-z0-9_]{0,63}$":{"$ref":"#/components/schemas/Localized_string"}},"description":"One facet whose value stands before the category's path under a fixed word, as brand pages do, per locale where\nit differs: `{ \"brand\": \"marken\" }` writes `/marken/arcteryx/outdoor-jacken/`. Default: none."},"separator":{"type":"string","description":"What joins a segment's parameter name and the value's slug. Default: `--`, which no slug holds, so `-` stays\npossible for a shop whose URLs read `color-red`."}},"required":["facets","combinations"],"description":"The combinations of facets whose values a path holds, and how a path spells them. A segment is the facet's parameter\nname, the separator and the value's slug, such as `farbe--grau`, and the segments follow the category's path in the\norder of `facets`, whatever order the shopper chose them in."},"TrailingSlash":{"oneOf":[{"type":"string","const":"as_written","description":"Each path as the catalog writes it."},{"type":"string","const":"always","description":"Every path ends in a slash: `/wohnen/sofas/`."},{"type":"string","const":"never","description":"No path ends in a slash but the root: `/wohnen/sofas`."}]},"UrlParameter":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/ParameterNames"}],"description":"A query parameter's name in the merchant's URLs: the name alone, or the name and the names it is also read by, such\nas an old shop's, so an indexed or bookmarked URL keeps working."},"ParameterNames":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","description":"The name URLs are written with. Default: the code."},"aliases":{"type":"array","items":{"type":"string"},"description":"Further names a URL may use, read as this parameter and written as its name."}}},"Redirects":{"type":"object","additionalProperties":false,"properties":{"$schema":{"type":"string","description":"The JSON Schema an editor checks this file against; kept as written."},"redirects":{"type":"array","items":{"$ref":"#/components/schemas/LegacyUrl"},"description":"Exact entries answer first, the patterns after them in the order written."}},"description":"`redirects.json`: the old shop's URLs."},"LegacyUrl":{"type":"object","additionalProperties":false,"properties":{"from":{"type":"string","description":"The old path, matched as a shop's paths are: ignoring case, a trailing slash and percent-encoding."},"pattern":{"type":"string","description":"Instead of `from`: a regular expression over the path, matched ignoring case, whose groups the `url` may name\nas `$1`. Patterns run only when no exact entry matched."},"query":{"type":"object","additionalProperties":{"type":"string"},"description":"With `from`: parameters the old URL must carry with these values, such as an old shop's category id; the\nothers are ignored."},"to":{"description":"The entity whose page it leads to now, such as `category:women/shoes` or `brand:bosch`.","$ref":"#/components/schemas/EntityRef"},"url":{"type":"string","description":"Instead of `to`: the URL it leads to now, a path such as `/sale` or one that starts with `https://`."},"status":{"type":"integer","format":"uint16","minimum":0,"maximum":65535,"description":"`301` by default; `308`, `302` and `307` are allowed, and `410` for a URL removed on purpose, which has no\ntarget.","enum":[301,302,307,308,410]}},"description":"One old URL: `{ \"from\": \"/damen/schuhe/sneaker.html\", \"to\": \"category:women/sneakers\" }`,\n`{ \"from\": \"/index.php\", \"query\": { \"cat\": \"12\" }, \"to\": \"category:women/shoes\" }`,\n`{ \"from\": \"/outlet-2019\", \"status\": 410 }` or `{ \"pattern\": \"^/product-category/(.+?)/?$\", \"url\": \"/$1\" }`."},"Limits":{"type":"object","properties":{"rules_per_release":{"type":"integer","format":"uint","minimum":0,"description":"Rules one `rules.json` may hold."},"description_length":{"type":"integer","format":"uint","minimum":0,"description":"Characters in a rule's description."},"pins_per_rule":{"type":"integer","format":"uint","minimum":0,"description":"Pins one rule may hold, at unique positions."},"pins_per_query":{"type":"integer","format":"uint","minimum":0,"description":"Pins one engine query may place."},"targets_per_query":{"type":"integer","format":"uint","minimum":0,"description":"Boost and bury targets in one engine query, which is one section of a response. The engine pays 2^n - 1\nsub-searches for n targets and silently drops weights beyond its scale fuel, which `compose.yaml` sets to pay\nfor exactly this many."},"keys_per_query":{"type":"integer","format":"uint","minimum":0,"description":"Engine rules one query switches on, pins and targets together. Each key costs the engine one unit of filter\nfuel; beyond this many, it drops rules."},"hidden_per_query":{"type":"integer","format":"uint","minimum":0,"description":"Entity ids one query may hide; hiding more for every request is an overlay."},"phrases_per_operator":{"type":"integer","format":"uint","minimum":0,"description":"Phrases in one `is`, `contains` or `starts_with`."},"nesting_depth":{"type":"integer","format":"uint","minimum":0,"description":"Levels of `any`, `all` and `not` in one expression."},"per_page":{"type":"integer","format":"uint32","minimum":0,"description":"Hits one page of a response may hold."},"reachable_hits":{"type":"integer","format":"uint64","minimum":0,"description":"Hits one search's pages reach, however many match: the engine ranks every hit up to the page asked for, so a\npage's time grows with its depth, and the deepest stays within the search latency budget. Totals are counted\nexactly past it; a response says how many hits are reachable."},"token_dimensions":{"type":"integer","format":"uint","minimum":0,"description":"Fields one type's combination tokens combine: the variant-level values its facets count. A variant holds up to\n2^n - 1 tokens for n of them, so twenty would make a million."},"filter_combinations":{"type":"integer","format":"uint","minimum":0,"description":"Combinations of chosen values one request's filters may name across the variant-level fields they narrow,\nsuch as three colors in two sizes for six."},"redirect_patterns":{"type":"integer","format":"uint","minimum":0,"description":"Patterns in `redirects.json`. Every URL no exact entry matches runs through all of them; a table of exact\nentries has no limit of its own."},"counted_facets":{"type":"integer","format":"uint","minimum":0,"description":"Facets a page counts on every request before the shopper opens any: the first of its layout, and every one a\nchoice or a request's `facets` names. The engine pays for each facet it counts each time, and the cost grows\nwith the facets, not with the values they hold, so a page of a hundred facets counts the first thirty and\nlists the rest without values (`deferred`)."},"facet_values":{"type":"integer","format":"uint32","minimum":0,"description":"Values a counted list or swatch facet carries before `more` says there are others: the most frequent ones, and\nevery chosen one. A request that names the facet in `facets` gets all of them, and a layout may set another\nnumber per facet (`limit`)."},"offers_per_call":{"type":"integer","format":"uint","minimum":0,"description":"Offer changes one `PUT /api/v1/offers` carries. A larger batch is refused whole, so a shop sends its changes in\nbatches of this size; each batch is one revision."},"placements_per_page":{"type":"integer","format":"uint","minimum":0,"description":"Banners one page of a response shows, top, grid and bottom together. Placements past it are left out in rule\norder, and the trace says so."},"synonyms_per_call":{"type":"integer","format":"uint","minimum":0,"description":"Synonym entries one add carries, such as the lines of one paste. A larger batch is refused whole."},"words_per_check":{"type":"integer","format":"uint","minimum":0,"description":"Words one synonym check searches for; an entry with more is refused whole."},"events_per_call":{"type":"integer","format":"uint","minimum":0,"description":"Clicks and views one `POST /v1/events` carries, well within the 64 kB a browser's beacon may send. A larger batch\nis refused whole."},"event_hours":{"type":"integer","format":"uint32","minimum":0,"description":"Hours after an answer that its clicks and views are taken and joined to its search; an event for an older\nanswer is refused by its query ID's time, without a lookup, and counted as late."}},"required":["rules_per_release","description_length","pins_per_rule","pins_per_query","targets_per_query","keys_per_query","hidden_per_query","phrases_per_operator","nesting_depth","per_page","reachable_hits","token_dimensions","filter_combinations","redirect_patterns","counted_facets","facet_values","offers_per_call","placements_per_page","synonyms_per_call","words_per_check","events_per_call","event_hours"]}}}}