{"openapi":"3.0.0","paths":{"/api/public/v1/profile":{"get":{"operationId":"PublicCatalogController_profile","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProfileEntity"}}}}},"security":[{"bearer":[]}],"tags":["public"]}},"/api/public/v1/policy":{"get":{"description":"Эффективный лимит запросов и потолок страницы для этого ключа, окна кэша ответов, минимальный срок предупреждения о депрекации и адрес политики. Читайте её вместо того, чтобы вшивать цифры документации в интеграцию.","operationId":"PublicCatalogController_policy","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPolicyEntity"}}}}},"security":[{"bearer":[]}],"summary":"Действующая политика контракта","tags":["public"]}},"/api/public/v1/properties":{"get":{"operationId":"PublicCatalogController_properties","parameters":[{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedPropertiesEntity"}}}}},"security":[{"bearer":[]}],"tags":["public"]}},"/api/public/v1/countries":{"get":{"description":"Страны происхождения брендов: alpha-2 (ключ фильтра country в /brands), alpha-3, цифровой код, телефонный код, ccTLD. Отдаётся целиком, без пагинации.","operationId":"PublicCatalogController_countries","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCountriesEntity"}}}}},"security":[{"bearer":[]}],"summary":"Справочник стран (ISO 3166-1)","tags":["public"]}},"/api/public/v1/measures":{"get":{"description":"Коды ОКЕИ с обозначениями. Поле unit связывает единицу с карточкой товара (unit: \"m2\"). Отдаётся целиком, без пагинации.","operationId":"PublicCatalogController_measures","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicMeasuresEntity"}}}}},"security":[{"bearer":[]}],"summary":"Справочник единиц измерения (ОКЕИ)","tags":["public"]}},"/api/public/v1/products":{"get":{"operationId":"PublicCatalogController_products","parameters":[{"name":"matched_only","required":false,"in":"query","description":"Только товары, сопоставленные с моей номенклатурой (загрузка прайса в кабинете, строки со статусом «сопоставлено автоматически» или «подтверждено»). Сужает выдачу до реального ассортимента дилера: и листинг, и дельты `/changes` (включая раздел `removed`), и качество среза в кабинете. Номенклатура не загружена или ни одна строка не сопоставлена — выдача пуста (это ответ «в вашем срезе ничего нет», а не ошибка).","schema":{"example":true,"type":"boolean"}},{"name":"updated_after","required":false,"in":"query","description":"Только товары, обновлённые после этого момента (ISO 8601).","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"created_after","required":false,"in":"query","description":"Только товары, добавленные после этого момента (ISO 8601) — лента новинок.","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"id","required":false,"in":"query","description":"Точечная выборка по id товара.","schema":{"type":"string"}},{"name":"q","required":false,"in":"query","description":"Полнотекстовый поиск. По названию товара — с учётом словоформ («плитки» находит «плитка») и устойчиво к опечаткам; по названию бренда и коллекции — подстрокой без учёта регистра. Запрос из одного «слова» с цифрой дополнительно ищется как артикул поставщика — точным совпадением по активным предложениям, и такие товары идут первыми. Результаты по умолчанию отсортированы по релевантности (см. sort).","schema":{"maxLength":100,"example":"124439","type":"string"}},{"name":"brand_id","required":false,"in":"query","description":"Фильтр по id бренда (повторяемый — несколько брендов через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"collection_id","required":false,"in":"query","description":"Фильтр по id коллекции (повторяемый — несколько коллекций через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"kind","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["tile","accessory"]}}},{"name":"placement","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["floor","wall","universal"]}}},{"name":"in_stock","required":false,"in":"query","description":"Только в наличии (true) / только нет (false).","schema":{"type":"boolean"}},{"name":"price_min","required":false,"in":"query","description":"Минимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"price_max","required":false,"in":"query","description":"Максимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"width_cm","required":false,"in":"query","description":"Ширина, см (точное совпадение).","schema":{"type":"number"}},{"name":"length_cm","required":false,"in":"query","description":"Длина, см (точное совпадение).","schema":{"type":"number"}},{"name":"width_min","required":false,"in":"query","description":"Ширина от, см (диапазон).","schema":{"type":"number"}},{"name":"width_max","required":false,"in":"query","description":"Ширина до, см (диапазон).","schema":{"type":"number"}},{"name":"length_min","required":false,"in":"query","description":"Длина от, см (диапазон).","schema":{"type":"number"}},{"name":"length_max","required":false,"in":"query","description":"Длина до, см (диапазон).","schema":{"type":"number"}},{"name":"thickness_min","required":false,"in":"query","description":"Толщина от, мм (диапазон).","schema":{"type":"number"}},{"name":"thickness_max","required":false,"in":"query","description":"Толщина до, мм (диапазон).","schema":{"type":"number"}},{"name":"format","required":false,"in":"query","description":"Формат \"ширинаxдлина\" в см, ориентация-независимо (60x120 = 120x60). Значение поля format в карточке. Повторяемый — несколько форматов через ИЛИ.","schema":{"example":"60x120","type":"array","items":{"type":"string"}}},{"name":"size_category","required":false,"in":"query","description":"Категория размера — бакет по большей стороне, см: small (<30), medium (30–60), large (60–120), grand (от 120). Значение поля size_category в карточке; доступные бакеты со счётчиками — в /products/facets. Повторяемый — несколько категорий через ИЛИ.","schema":{"example":"large","type":"array","items":{"type":"string","enum":["small","medium","large","grand"]}}},{"name":"category","required":false,"in":"query","description":"Раздел каталога по slug (повторяемый — несколько разделов через ИЛИ). Включает ПОДРАЗДЕЛЫ: category=keramogranit отдаёт и товары его подкатегорий. Дерево разделов со счётчиками — в /categories. Не путать с характеристикой material (attr=material:…) — та описывает состав товара, а не раздел каталога.","schema":{"example":"keramogranit","type":"array","items":{"type":"string"}}},{"name":"attr","required":false,"in":"query","description":"Фильтр по характеристикам, формат \"ключ:slug\" (повторяемый): attr=color:bezhevyi&attr=material:keramogranit. Значения ОДНОГО ключа — ИЛИ (мультивыбор чипов расширяет выборку), разные ключи — И.","schema":{"example":["color:bezhevyi"],"type":"array","items":{"type":"string"}}},{"name":"fields","required":false,"in":"query","description":"Sparse fieldsets: список полей ответа через запятую (`id` всегда включён) — урезает payload. Пример: `fields=id,name,price_min,currency`. Без `properties` в списке характеристики не выбираются из БД — запрос дешевле и быстрее.","schema":{"example":"id,name,price_min,currency","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Сортировка: relevance — по релевантности поиска (только вместе с q; при заданном q это порядок по умолчанию, без q игнорируется); price/-price — по мин. цене активных офферов (товары без цены — в конце); updated_at/-updated_at — по времени изменения (поток изменений для sync); created_at/-created_at — по времени добавления (-created_at = новинки сверху); popular — по курируемому рангу популярности (меньше = популярнее, не прокурированные — в конце). По умолчанию — по стабильному id.","schema":{"type":"string","enum":["relevance","price","-price","updated_at","-updated_at","created_at","-created_at","popular"]}},{"name":"with_total","required":false,"in":"query","description":"Вернуть best-effort `total` в page-meta (доп. count по фильтру). По умолчанию не считается (экономия на горячем пути).","schema":{"type":"boolean"}},{"name":"segment","required":false,"in":"query","description":"Применить сохранённый срез («мой ассортимент») по id: сервер разворачивает его в свой набор фильтров вместо длинной строки запроса. Срез создаётся в кабинете («Мои срезы»), там же видно его id. Явные параметры запроса перекрывают срез: `?segment=<id>&in_stock=true` — срез плюс сужение. Чужой/несуществующий id — 404.","schema":{"example":"clz1segment0000example","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedProductsEntity"}}}}},"security":[{"bearer":[]}],"tags":["public"]}},"/api/public/v1/products/export":{"get":{"description":"Полная выгрузка среза каталога построчным JSON (`application/x-ndjson`): один товар — одна строка. Для первичной заливки: не нужно листать страницы. Принимает те же фильтры и `fields`, что `/products` (пагинация/сортировка не применимы — порядок по id). Дальше держать каталог в актуальном состоянии дешевле через `updated_after` + `/product-ids`.","operationId":"PublicCatalogController_exportProducts","parameters":[{"name":"matched_only","required":false,"in":"query","description":"Только товары, сопоставленные с моей номенклатурой (загрузка прайса в кабинете, строки со статусом «сопоставлено автоматически» или «подтверждено»). Сужает выдачу до реального ассортимента дилера: и листинг, и дельты `/changes` (включая раздел `removed`), и качество среза в кабинете. Номенклатура не загружена или ни одна строка не сопоставлена — выдача пуста (это ответ «в вашем срезе ничего нет», а не ошибка).","schema":{"example":true,"type":"boolean"}},{"name":"updated_after","required":false,"in":"query","description":"Только товары, обновлённые после этого момента (ISO 8601).","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"created_after","required":false,"in":"query","description":"Только товары, добавленные после этого момента (ISO 8601) — лента новинок.","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"id","required":false,"in":"query","description":"Точечная выборка по id товара.","schema":{"type":"string"}},{"name":"q","required":false,"in":"query","description":"Полнотекстовый поиск. По названию товара — с учётом словоформ («плитки» находит «плитка») и устойчиво к опечаткам; по названию бренда и коллекции — подстрокой без учёта регистра. Запрос из одного «слова» с цифрой дополнительно ищется как артикул поставщика — точным совпадением по активным предложениям, и такие товары идут первыми. Результаты по умолчанию отсортированы по релевантности (см. sort).","schema":{"maxLength":100,"example":"124439","type":"string"}},{"name":"brand_id","required":false,"in":"query","description":"Фильтр по id бренда (повторяемый — несколько брендов через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"collection_id","required":false,"in":"query","description":"Фильтр по id коллекции (повторяемый — несколько коллекций через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"kind","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["tile","accessory"]}}},{"name":"placement","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["floor","wall","universal"]}}},{"name":"in_stock","required":false,"in":"query","description":"Только в наличии (true) / только нет (false).","schema":{"type":"boolean"}},{"name":"price_min","required":false,"in":"query","description":"Минимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"price_max","required":false,"in":"query","description":"Максимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"width_cm","required":false,"in":"query","description":"Ширина, см (точное совпадение).","schema":{"type":"number"}},{"name":"length_cm","required":false,"in":"query","description":"Длина, см (точное совпадение).","schema":{"type":"number"}},{"name":"width_min","required":false,"in":"query","description":"Ширина от, см (диапазон).","schema":{"type":"number"}},{"name":"width_max","required":false,"in":"query","description":"Ширина до, см (диапазон).","schema":{"type":"number"}},{"name":"length_min","required":false,"in":"query","description":"Длина от, см (диапазон).","schema":{"type":"number"}},{"name":"length_max","required":false,"in":"query","description":"Длина до, см (диапазон).","schema":{"type":"number"}},{"name":"thickness_min","required":false,"in":"query","description":"Толщина от, мм (диапазон).","schema":{"type":"number"}},{"name":"thickness_max","required":false,"in":"query","description":"Толщина до, мм (диапазон).","schema":{"type":"number"}},{"name":"format","required":false,"in":"query","description":"Формат \"ширинаxдлина\" в см, ориентация-независимо (60x120 = 120x60). Значение поля format в карточке. Повторяемый — несколько форматов через ИЛИ.","schema":{"example":"60x120","type":"array","items":{"type":"string"}}},{"name":"size_category","required":false,"in":"query","description":"Категория размера — бакет по большей стороне, см: small (<30), medium (30–60), large (60–120), grand (от 120). Значение поля size_category в карточке; доступные бакеты со счётчиками — в /products/facets. Повторяемый — несколько категорий через ИЛИ.","schema":{"example":"large","type":"array","items":{"type":"string","enum":["small","medium","large","grand"]}}},{"name":"category","required":false,"in":"query","description":"Раздел каталога по slug (повторяемый — несколько разделов через ИЛИ). Включает ПОДРАЗДЕЛЫ: category=keramogranit отдаёт и товары его подкатегорий. Дерево разделов со счётчиками — в /categories. Не путать с характеристикой material (attr=material:…) — та описывает состав товара, а не раздел каталога.","schema":{"example":"keramogranit","type":"array","items":{"type":"string"}}},{"name":"attr","required":false,"in":"query","description":"Фильтр по характеристикам, формат \"ключ:slug\" (повторяемый): attr=color:bezhevyi&attr=material:keramogranit. Значения ОДНОГО ключа — ИЛИ (мультивыбор чипов расширяет выборку), разные ключи — И.","schema":{"example":["color:bezhevyi"],"type":"array","items":{"type":"string"}}},{"name":"fields","required":false,"in":"query","description":"Sparse fieldsets: список полей ответа через запятую (`id` всегда включён) — урезает payload. Пример: `fields=id,name,price_min,currency`. Без `properties` в списке характеристики не выбираются из БД — запрос дешевле и быстрее.","schema":{"example":"id,name,price_min,currency","type":"string"}},{"name":"segment","required":false,"in":"query","description":"Применить сохранённый срез («мой ассортимент») по id: сервер разворачивает его в свой набор фильтров вместо длинной строки запроса. Срез создаётся в кабинете («Мои срезы»), там же видно его id. Явные параметры запроса перекрывают срез: `?segment=<id>&in_stock=true` — срез плюс сужение. Чужой/несуществующий id — 404.","schema":{"example":"clz1segment0000example","type":"string"}}],"responses":{"200":{"description":"Поток строк JSON (по товару на строку).","content":{"application/x-ndjson; charset=utf-8":{"schema":{"type":"string","format":"binary"}}}}},"security":[{"bearer":[]}],"summary":"Bulk-export товаров (NDJSON)","tags":["public"]}},"/api/public/v1/products/facets":{"get":{"operationId":"PublicCatalogController_facets","parameters":[{"name":"matched_only","required":false,"in":"query","description":"Только товары, сопоставленные с моей номенклатурой (загрузка прайса в кабинете, строки со статусом «сопоставлено автоматически» или «подтверждено»). Сужает выдачу до реального ассортимента дилера: и листинг, и дельты `/changes` (включая раздел `removed`), и качество среза в кабинете. Номенклатура не загружена или ни одна строка не сопоставлена — выдача пуста (это ответ «в вашем срезе ничего нет», а не ошибка).","schema":{"example":true,"type":"boolean"}},{"name":"updated_after","required":false,"in":"query","description":"Только товары, обновлённые после этого момента (ISO 8601).","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"created_after","required":false,"in":"query","description":"Только товары, добавленные после этого момента (ISO 8601) — лента новинок.","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"id","required":false,"in":"query","description":"Точечная выборка по id товара.","schema":{"type":"string"}},{"name":"q","required":false,"in":"query","description":"Полнотекстовый поиск. По названию товара — с учётом словоформ («плитки» находит «плитка») и устойчиво к опечаткам; по названию бренда и коллекции — подстрокой без учёта регистра. Запрос из одного «слова» с цифрой дополнительно ищется как артикул поставщика — точным совпадением по активным предложениям, и такие товары идут первыми. Результаты по умолчанию отсортированы по релевантности (см. sort).","schema":{"maxLength":100,"example":"124439","type":"string"}},{"name":"brand_id","required":false,"in":"query","description":"Фильтр по id бренда (повторяемый — несколько брендов через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"collection_id","required":false,"in":"query","description":"Фильтр по id коллекции (повторяемый — несколько коллекций через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"kind","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["tile","accessory"]}}},{"name":"placement","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["floor","wall","universal"]}}},{"name":"in_stock","required":false,"in":"query","description":"Только в наличии (true) / только нет (false).","schema":{"type":"boolean"}},{"name":"price_min","required":false,"in":"query","description":"Минимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"price_max","required":false,"in":"query","description":"Максимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"width_cm","required":false,"in":"query","description":"Ширина, см (точное совпадение).","schema":{"type":"number"}},{"name":"length_cm","required":false,"in":"query","description":"Длина, см (точное совпадение).","schema":{"type":"number"}},{"name":"width_min","required":false,"in":"query","description":"Ширина от, см (диапазон).","schema":{"type":"number"}},{"name":"width_max","required":false,"in":"query","description":"Ширина до, см (диапазон).","schema":{"type":"number"}},{"name":"length_min","required":false,"in":"query","description":"Длина от, см (диапазон).","schema":{"type":"number"}},{"name":"length_max","required":false,"in":"query","description":"Длина до, см (диапазон).","schema":{"type":"number"}},{"name":"thickness_min","required":false,"in":"query","description":"Толщина от, мм (диапазон).","schema":{"type":"number"}},{"name":"thickness_max","required":false,"in":"query","description":"Толщина до, мм (диапазон).","schema":{"type":"number"}},{"name":"format","required":false,"in":"query","description":"Формат \"ширинаxдлина\" в см, ориентация-независимо (60x120 = 120x60). Значение поля format в карточке. Повторяемый — несколько форматов через ИЛИ.","schema":{"example":"60x120","type":"array","items":{"type":"string"}}},{"name":"size_category","required":false,"in":"query","description":"Категория размера — бакет по большей стороне, см: small (<30), medium (30–60), large (60–120), grand (от 120). Значение поля size_category в карточке; доступные бакеты со счётчиками — в /products/facets. Повторяемый — несколько категорий через ИЛИ.","schema":{"example":"large","type":"array","items":{"type":"string","enum":["small","medium","large","grand"]}}},{"name":"category","required":false,"in":"query","description":"Раздел каталога по slug (повторяемый — несколько разделов через ИЛИ). Включает ПОДРАЗДЕЛЫ: category=keramogranit отдаёт и товары его подкатегорий. Дерево разделов со счётчиками — в /categories. Не путать с характеристикой material (attr=material:…) — та описывает состав товара, а не раздел каталога.","schema":{"example":"keramogranit","type":"array","items":{"type":"string"}}},{"name":"attr","required":false,"in":"query","description":"Фильтр по характеристикам, формат \"ключ:slug\" (повторяемый): attr=color:bezhevyi&attr=material:keramogranit. Значения ОДНОГО ключа — ИЛИ (мультивыбор чипов расширяет выборку), разные ключи — И.","schema":{"example":["color:bezhevyi"],"type":"array","items":{"type":"string"}}},{"name":"fields","required":false,"in":"query","description":"Sparse fieldsets: список полей ответа через запятую (`id` всегда включён) — урезает payload. Пример: `fields=id,name,price_min,currency`. Без `properties` в списке характеристики не выбираются из БД — запрос дешевле и быстрее.","schema":{"example":"id,name,price_min,currency","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Сортировка: relevance — по релевантности поиска (только вместе с q; при заданном q это порядок по умолчанию, без q игнорируется); price/-price — по мин. цене активных офферов (товары без цены — в конце); updated_at/-updated_at — по времени изменения (поток изменений для sync); created_at/-created_at — по времени добавления (-created_at = новинки сверху); popular — по курируемому рангу популярности (меньше = популярнее, не прокурированные — в конце). По умолчанию — по стабильному id.","schema":{"type":"string","enum":["relevance","price","-price","updated_at","-updated_at","created_at","-created_at","popular"]}},{"name":"with_total","required":false,"in":"query","description":"Вернуть best-effort `total` в page-meta (доп. count по фильтру). По умолчанию не считается (экономия на горячем пути).","schema":{"type":"boolean"}},{"name":"segment","required":false,"in":"query","description":"Применить сохранённый срез («мой ассортимент») по id: сервер разворачивает его в свой набор фильтров вместо длинной строки запроса. Срез создаётся в кабинете («Мои срезы»), там же видно его id. Явные параметры запроса перекрывают срез: `?segment=<id>&in_stock=true` — срез плюс сужение. Чужой/несуществующий id — 404.","schema":{"example":"clz1segment0000example","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicFacetsEntity"}}}}},"security":[{"bearer":[]}],"tags":["public"]}},"/api/public/v1/products/{idOrSlug}":{"get":{"description":"Товар по `id`, `public_id` или `slug` (SEO-URL витрины): размеры, формат, цена, картинки и характеристики. Характеристики отдаются всегда (в листинге — по `fields`). Для своей номенклатуры используйте `public_id`: он неизменен и продолжает работать даже если карточку склеили с дублем — запрос отдаст канон, в который её склеили.","operationId":"PublicCatalogController_product","parameters":[{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProductEntity"}}}},"404":{"description":"Товар не найден."}},"security":[{"bearer":[]}],"summary":"Карточка товара","tags":["public"]}},"/api/public/v1/products/{id}/offers":{"get":{"description":"Принимает те же ключи, что карточка: `id`, `public_id` (в т.ч. выбывший после склейки) или `slug`. `product_id` в ответе — id живого канона.","operationId":"PublicCatalogController_productOffers","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductOffersEntity"}}}}},"security":[{"bearer":[]}],"summary":"Предложения поставщиков по товару","tags":["public"]}},"/api/public/v1/product-ids":{"get":{"description":"Для дифа полноты на своей стороне (что появилось/исчезло) без выкачки всего каталога. Отдаёт public_id — он переживает склейку дублей, в отличие от внутреннего id. `segment=<id>` (и/или `matched_only=true`) сужает дамп до своего среза — полная синхронизация СВОЕГО ассортимента без выкачки id по всей вертикали.","operationId":"PublicCatalogController_productIds","parameters":[{"name":"matched_only","required":false,"in":"query","description":"Только товары, сопоставленные с моей номенклатурой (загрузка прайса в кабинете, строки со статусом «сопоставлено автоматически» или «подтверждено»). Сужает выдачу до реального ассортимента дилера: и листинг, и дельты `/changes` (включая раздел `removed`), и качество среза в кабинете. Номенклатура не загружена или ни одна строка не сопоставлена — выдача пуста (это ответ «в вашем срезе ничего нет», а не ошибка).","schema":{"example":true,"type":"boolean"}},{"name":"updated_after","required":false,"in":"query","description":"Только товары, обновлённые после этого момента (ISO 8601).","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"created_after","required":false,"in":"query","description":"Только товары, добавленные после этого момента (ISO 8601) — лента новинок.","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"id","required":false,"in":"query","description":"Точечная выборка по id товара.","schema":{"type":"string"}},{"name":"q","required":false,"in":"query","description":"Полнотекстовый поиск. По названию товара — с учётом словоформ («плитки» находит «плитка») и устойчиво к опечаткам; по названию бренда и коллекции — подстрокой без учёта регистра. Запрос из одного «слова» с цифрой дополнительно ищется как артикул поставщика — точным совпадением по активным предложениям, и такие товары идут первыми. Результаты по умолчанию отсортированы по релевантности (см. sort).","schema":{"maxLength":100,"example":"124439","type":"string"}},{"name":"brand_id","required":false,"in":"query","description":"Фильтр по id бренда (повторяемый — несколько брендов через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"collection_id","required":false,"in":"query","description":"Фильтр по id коллекции (повторяемый — несколько коллекций через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"kind","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["tile","accessory"]}}},{"name":"placement","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["floor","wall","universal"]}}},{"name":"in_stock","required":false,"in":"query","description":"Только в наличии (true) / только нет (false).","schema":{"type":"boolean"}},{"name":"price_min","required":false,"in":"query","description":"Минимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"price_max","required":false,"in":"query","description":"Максимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"width_cm","required":false,"in":"query","description":"Ширина, см (точное совпадение).","schema":{"type":"number"}},{"name":"length_cm","required":false,"in":"query","description":"Длина, см (точное совпадение).","schema":{"type":"number"}},{"name":"width_min","required":false,"in":"query","description":"Ширина от, см (диапазон).","schema":{"type":"number"}},{"name":"width_max","required":false,"in":"query","description":"Ширина до, см (диапазон).","schema":{"type":"number"}},{"name":"length_min","required":false,"in":"query","description":"Длина от, см (диапазон).","schema":{"type":"number"}},{"name":"length_max","required":false,"in":"query","description":"Длина до, см (диапазон).","schema":{"type":"number"}},{"name":"thickness_min","required":false,"in":"query","description":"Толщина от, мм (диапазон).","schema":{"type":"number"}},{"name":"thickness_max","required":false,"in":"query","description":"Толщина до, мм (диапазон).","schema":{"type":"number"}},{"name":"format","required":false,"in":"query","description":"Формат \"ширинаxдлина\" в см, ориентация-независимо (60x120 = 120x60). Значение поля format в карточке. Повторяемый — несколько форматов через ИЛИ.","schema":{"example":"60x120","type":"array","items":{"type":"string"}}},{"name":"size_category","required":false,"in":"query","description":"Категория размера — бакет по большей стороне, см: small (<30), medium (30–60), large (60–120), grand (от 120). Значение поля size_category в карточке; доступные бакеты со счётчиками — в /products/facets. Повторяемый — несколько категорий через ИЛИ.","schema":{"example":"large","type":"array","items":{"type":"string","enum":["small","medium","large","grand"]}}},{"name":"category","required":false,"in":"query","description":"Раздел каталога по slug (повторяемый — несколько разделов через ИЛИ). Включает ПОДРАЗДЕЛЫ: category=keramogranit отдаёт и товары его подкатегорий. Дерево разделов со счётчиками — в /categories. Не путать с характеристикой material (attr=material:…) — та описывает состав товара, а не раздел каталога.","schema":{"example":"keramogranit","type":"array","items":{"type":"string"}}},{"name":"attr","required":false,"in":"query","description":"Фильтр по характеристикам, формат \"ключ:slug\" (повторяемый): attr=color:bezhevyi&attr=material:keramogranit. Значения ОДНОГО ключа — ИЛИ (мультивыбор чипов расширяет выборку), разные ключи — И.","schema":{"example":["color:bezhevyi"],"type":"array","items":{"type":"string"}}},{"name":"fields","required":false,"in":"query","description":"Sparse fieldsets: список полей ответа через запятую (`id` всегда включён) — урезает payload. Пример: `fields=id,name,price_min,currency`. Без `properties` в списке характеристики не выбираются из БД — запрос дешевле и быстрее.","schema":{"example":"id,name,price_min,currency","type":"string"}},{"name":"segment","required":false,"in":"query","description":"Применить сохранённый срез («мой ассортимент») по id: сервер разворачивает его в свой набор фильтров вместо длинной строки запроса. Срез создаётся в кабинете («Мои срезы»), там же видно его id. Явные параметры запроса перекрывают срез: `?segment=<id>&in_stock=true` — срез плюс сужение. Чужой/несуществующий id — 404.","schema":{"example":"clz1segment0000example","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductIdsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Полный список public_id товаров вертикали","tags":["public"]}},"/api/public/v1/brands":{"get":{"description":"Опц. фильтры: slug (bulk по списку, макс. 50), country (alpha-2), category (slug material), q (поиск по имени), supplier (slug поставщика, повторяемый — ИЛИ), in_stock (есть/нет товара в наличии), products_count_min/max и collections_count_min/max (диапазоны тех же счётчиков, что в карточке бренда). Значения country/category — в brands/facets, suppliers[] — в самой карточке бренда. Сортировка: sort=name/-name (алфавит) или updated_at/-updated_at; по умолчанию — по стабильному id.","operationId":"PublicCatalogController_brands","parameters":[{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"slug","required":false,"in":"query","description":"Bulk-выборка брендов по списку slug (повторяемый — несколько slug через ИЛИ, максимум 50). Пустой список параметр не задаёт — фильтр игнорируется. Для витрины, которой нужно N брендов одним запросом (напр. топ-бренды лендинга с логотипами) вместо N отдельных GET /brands/:slug.","schema":{"example":["brand-a","brand-b"],"type":"array","items":{"type":"string"}}},{"name":"country","required":false,"in":"query","description":"Страна бренда — ISO 3166-1 alpha-2 (см. brands/facets).","schema":{"example":"ES","type":"string"}},{"name":"category","required":false,"in":"query","description":"Категория бренда — slug значения material его товаров (см. brands/facets).","schema":{"example":"keramogranit","type":"string"}},{"name":"q","required":false,"in":"query","description":"Поиск по имени бренда (без учёта регистра).","schema":{"example":"cifre","type":"string"}},{"name":"sort","required":false,"in":"query","description":"Сортировка: name/-name — по имени бренда (алфавит, коллация БД); updated_at/-updated_at — по времени изменения. По умолчанию — по стабильному id.","schema":{"type":"string","enum":["name","-name","updated_at","-updated_at"]}},{"name":"supplier","required":false,"in":"query","description":"Фильтр по slug поставщика (повторяемый — несколько поставщиков через ИЛИ). Те же slug, что в suppliers[] карточки/листинга брендов; поставщик без права публикации (isPublic=false) под этим фильтром брендов не даёт.","schema":{"example":"fiesta-ceramica","type":"array","items":{"type":"string"}}},{"name":"in_stock","required":false,"in":"query","description":"Только бренды минимум с одним товаром в наличии (true) / только без ни одного (false). Наличие — по публичному агрегату товара (то же поле, что в его карточке).","schema":{"type":"boolean"}},{"name":"products_count_min","required":false,"in":"query","description":"Число товаров бренда от (включительно) — тот же счётчик, что products_count в контракте.","schema":{"type":"number"}},{"name":"products_count_max","required":false,"in":"query","description":"Число товаров бренда до (включительно) — тот же счётчик, что products_count в контракте.","schema":{"type":"number"}},{"name":"collections_count_min","required":false,"in":"query","description":"Число коллекций бренда от (включительно) — тот же счётчик, что collections_count в контракте.","schema":{"type":"number"}},{"name":"collections_count_max","required":false,"in":"query","description":"Число коллекций бренда до (включительно) — тот же счётчик, что collections_count в контракте.","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedBrandsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Бренды каталога","tags":["public"]}},"/api/public/v1/brands/facets":{"get":{"description":"Страны и категории брендов со счётчиками — значения для фильтров GET /brands. Глобальные по вертикали клиента: НЕ пересчитываются от применённых фильтров.","operationId":"PublicCatalogController_brandFacets","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBrandFacetsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Фасеты справочника брендов","tags":["public"]}},"/api/public/v1/brands/categories/{slug}":{"get":{"description":"Резолвит slug значения material (текущий или устаревший алиас после смены курации) в канонический — для 301-редиректа со старого брендового лендинга директории. Неизвестный slug — 404.","operationId":"PublicCatalogController_resolveBrandCategory","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryResolveEntity"}}}},"404":{"description":"Категория не найдена."}},"security":[{"bearer":[]}],"summary":"Канонический slug категории брендов","tags":["public"]}},"/api/public/v1/brands/{idOrSlug}":{"get":{"description":"Бренд по id или slug (SEO-URL витрины): счётчики товаров/коллекций, derived-категории, логотип с вариантами.","operationId":"PublicCatalogController_brand","parameters":[{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBrandEntity"}}}},"404":{"description":"Бренд не найден."}},"security":[{"bearer":[]}],"summary":"Карточка бренда","tags":["public"]}},"/api/public/v1/collections":{"get":{"description":"Листинг коллекций с ТЕМ ЖЕ набором фильтров, что у /products (brand_id, category, attr, format, in_stock, price_min/max, q, …): коллекция попадает в выдачу, если фильтру соответствует хотя бы один её товар. Сортировка: popular (по числу товаров), price/-price, name/-name, updated_at/-updated_at. Счётчики и диапазон цен в карточке — денорм-агрегаты по публичным товарам коллекции. Коллекция без единого публичного товара наружу не идёт.","operationId":"PublicCatalogController_collections","parameters":[{"name":"matched_only","required":false,"in":"query","description":"Только товары, сопоставленные с моей номенклатурой (загрузка прайса в кабинете, строки со статусом «сопоставлено автоматически» или «подтверждено»). Сужает выдачу до реального ассортимента дилера: и листинг, и дельты `/changes` (включая раздел `removed`), и качество среза в кабинете. Номенклатура не загружена или ни одна строка не сопоставлена — выдача пуста (это ответ «в вашем срезе ничего нет», а не ошибка).","schema":{"example":true,"type":"boolean"}},{"name":"updated_after","required":false,"in":"query","description":"Только товары, обновлённые после этого момента (ISO 8601).","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"created_after","required":false,"in":"query","description":"Только товары, добавленные после этого момента (ISO 8601) — лента новинок.","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"q","required":false,"in":"query","description":"Полнотекстовый поиск. По названию товара — с учётом словоформ («плитки» находит «плитка») и устойчиво к опечаткам; по названию бренда и коллекции — подстрокой без учёта регистра. Запрос из одного «слова» с цифрой дополнительно ищется как артикул поставщика — точным совпадением по активным предложениям, и такие товары идут первыми. Результаты по умолчанию отсортированы по релевантности (см. sort).","schema":{"maxLength":100,"example":"124439","type":"string"}},{"name":"brand_id","required":false,"in":"query","description":"Фильтр по id бренда (повторяемый — несколько брендов через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"collection_id","required":false,"in":"query","description":"Фильтр по id коллекции (повторяемый — несколько коллекций через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"kind","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["tile","accessory"]}}},{"name":"placement","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["floor","wall","universal"]}}},{"name":"in_stock","required":false,"in":"query","description":"Только в наличии (true) / только нет (false).","schema":{"type":"boolean"}},{"name":"price_min","required":false,"in":"query","description":"Минимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"price_max","required":false,"in":"query","description":"Максимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"width_cm","required":false,"in":"query","description":"Ширина, см (точное совпадение).","schema":{"type":"number"}},{"name":"length_cm","required":false,"in":"query","description":"Длина, см (точное совпадение).","schema":{"type":"number"}},{"name":"width_min","required":false,"in":"query","description":"Ширина от, см (диапазон).","schema":{"type":"number"}},{"name":"width_max","required":false,"in":"query","description":"Ширина до, см (диапазон).","schema":{"type":"number"}},{"name":"length_min","required":false,"in":"query","description":"Длина от, см (диапазон).","schema":{"type":"number"}},{"name":"length_max","required":false,"in":"query","description":"Длина до, см (диапазон).","schema":{"type":"number"}},{"name":"thickness_min","required":false,"in":"query","description":"Толщина от, мм (диапазон).","schema":{"type":"number"}},{"name":"thickness_max","required":false,"in":"query","description":"Толщина до, мм (диапазон).","schema":{"type":"number"}},{"name":"format","required":false,"in":"query","description":"Формат \"ширинаxдлина\" в см, ориентация-независимо (60x120 = 120x60). Значение поля format в карточке. Повторяемый — несколько форматов через ИЛИ.","schema":{"example":"60x120","type":"array","items":{"type":"string"}}},{"name":"size_category","required":false,"in":"query","description":"Категория размера — бакет по большей стороне, см: small (<30), medium (30–60), large (60–120), grand (от 120). Значение поля size_category в карточке; доступные бакеты со счётчиками — в /products/facets. Повторяемый — несколько категорий через ИЛИ.","schema":{"example":"large","type":"array","items":{"type":"string","enum":["small","medium","large","grand"]}}},{"name":"category","required":false,"in":"query","description":"Раздел каталога по slug (повторяемый — несколько разделов через ИЛИ). Включает ПОДРАЗДЕЛЫ: category=keramogranit отдаёт и товары его подкатегорий. Дерево разделов со счётчиками — в /categories. Не путать с характеристикой material (attr=material:…) — та описывает состав товара, а не раздел каталога.","schema":{"example":"keramogranit","type":"array","items":{"type":"string"}}},{"name":"attr","required":false,"in":"query","description":"Фильтр по характеристикам, формат \"ключ:slug\" (повторяемый): attr=color:bezhevyi&attr=material:keramogranit. Значения ОДНОГО ключа — ИЛИ (мультивыбор чипов расширяет выборку), разные ключи — И.","schema":{"example":["color:bezhevyi"],"type":"array","items":{"type":"string"}}},{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Сортировка: popular — популярные сверху (по числу публичных товаров коллекции); price/-price — по цене «от» (коллекции без цены — в конце); name/-name — по алфавиту; updated_at/-updated_at — по времени изменения снимка; first_product_at/-first_product_at — по появлению самого раннего товара (ось «Новинки»; коллекции без товаров — в конце). По умолчанию — по стабильному id.","schema":{"type":"string","enum":["popular","price","-price","name","-name","updated_at","-updated_at","first_product_at","-first_product_at"]}},{"name":"with_total","required":false,"in":"query","description":"Вернуть best-effort `total` в page-meta (доп. count по фильтру). По умолчанию не считается (экономия на горячем пути).","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedCollectionsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Коллекции каталога","tags":["public"]}},"/api/public/v1/collections/facets":{"get":{"description":"Та же панель фильтров, что у /products/facets, и те же фильтры, что у /collections, но счётчик каждого значения — число РАЗЛИЧНЫХ коллекций, у которых есть подходящий публичный товар (тот же предикат, что у листинга /collections: `products.some`), а не число товаров. Используется режимом отображения «Коллекции» витрины.","operationId":"PublicCatalogController_collectionFacets","parameters":[{"name":"matched_only","required":false,"in":"query","description":"Только товары, сопоставленные с моей номенклатурой (загрузка прайса в кабинете, строки со статусом «сопоставлено автоматически» или «подтверждено»). Сужает выдачу до реального ассортимента дилера: и листинг, и дельты `/changes` (включая раздел `removed`), и качество среза в кабинете. Номенклатура не загружена или ни одна строка не сопоставлена — выдача пуста (это ответ «в вашем срезе ничего нет», а не ошибка).","schema":{"example":true,"type":"boolean"}},{"name":"updated_after","required":false,"in":"query","description":"Только товары, обновлённые после этого момента (ISO 8601).","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"created_after","required":false,"in":"query","description":"Только товары, добавленные после этого момента (ISO 8601) — лента новинок.","schema":{"example":"2026-06-01T00:00:00Z","type":"string"}},{"name":"id","required":false,"in":"query","description":"Точечная выборка по id товара.","schema":{"type":"string"}},{"name":"q","required":false,"in":"query","description":"Полнотекстовый поиск. По названию товара — с учётом словоформ («плитки» находит «плитка») и устойчиво к опечаткам; по названию бренда и коллекции — подстрокой без учёта регистра. Запрос из одного «слова» с цифрой дополнительно ищется как артикул поставщика — точным совпадением по активным предложениям, и такие товары идут первыми. Результаты по умолчанию отсортированы по релевантности (см. sort).","schema":{"maxLength":100,"example":"124439","type":"string"}},{"name":"brand_id","required":false,"in":"query","description":"Фильтр по id бренда (повторяемый — несколько брендов через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"collection_id","required":false,"in":"query","description":"Фильтр по id коллекции (повторяемый — несколько коллекций через ИЛИ).","schema":{"type":"array","items":{"type":"string"}}},{"name":"kind","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["tile","accessory"]}}},{"name":"placement","required":false,"in":"query","schema":{"type":"array","items":{"type":"string","enum":["floor","wall","universal"]}}},{"name":"in_stock","required":false,"in":"query","description":"Только в наличии (true) / только нет (false).","schema":{"type":"boolean"}},{"name":"price_min","required":false,"in":"query","description":"Минимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"price_max","required":false,"in":"query","description":"Максимальная цена — та же, что видна в карточке, руб.","schema":{"type":"number"}},{"name":"width_cm","required":false,"in":"query","description":"Ширина, см (точное совпадение).","schema":{"type":"number"}},{"name":"length_cm","required":false,"in":"query","description":"Длина, см (точное совпадение).","schema":{"type":"number"}},{"name":"width_min","required":false,"in":"query","description":"Ширина от, см (диапазон).","schema":{"type":"number"}},{"name":"width_max","required":false,"in":"query","description":"Ширина до, см (диапазон).","schema":{"type":"number"}},{"name":"length_min","required":false,"in":"query","description":"Длина от, см (диапазон).","schema":{"type":"number"}},{"name":"length_max","required":false,"in":"query","description":"Длина до, см (диапазон).","schema":{"type":"number"}},{"name":"thickness_min","required":false,"in":"query","description":"Толщина от, мм (диапазон).","schema":{"type":"number"}},{"name":"thickness_max","required":false,"in":"query","description":"Толщина до, мм (диапазон).","schema":{"type":"number"}},{"name":"format","required":false,"in":"query","description":"Формат \"ширинаxдлина\" в см, ориентация-независимо (60x120 = 120x60). Значение поля format в карточке. Повторяемый — несколько форматов через ИЛИ.","schema":{"example":"60x120","type":"array","items":{"type":"string"}}},{"name":"size_category","required":false,"in":"query","description":"Категория размера — бакет по большей стороне, см: small (<30), medium (30–60), large (60–120), grand (от 120). Значение поля size_category в карточке; доступные бакеты со счётчиками — в /products/facets. Повторяемый — несколько категорий через ИЛИ.","schema":{"example":"large","type":"array","items":{"type":"string","enum":["small","medium","large","grand"]}}},{"name":"category","required":false,"in":"query","description":"Раздел каталога по slug (повторяемый — несколько разделов через ИЛИ). Включает ПОДРАЗДЕЛЫ: category=keramogranit отдаёт и товары его подкатегорий. Дерево разделов со счётчиками — в /categories. Не путать с характеристикой material (attr=material:…) — та описывает состав товара, а не раздел каталога.","schema":{"example":"keramogranit","type":"array","items":{"type":"string"}}},{"name":"attr","required":false,"in":"query","description":"Фильтр по характеристикам, формат \"ключ:slug\" (повторяемый): attr=color:bezhevyi&attr=material:keramogranit. Значения ОДНОГО ключа — ИЛИ (мультивыбор чипов расширяет выборку), разные ключи — И.","schema":{"example":["color:bezhevyi"],"type":"array","items":{"type":"string"}}},{"name":"fields","required":false,"in":"query","description":"Sparse fieldsets: список полей ответа через запятую (`id` всегда включён) — урезает payload. Пример: `fields=id,name,price_min,currency`. Без `properties` в списке характеристики не выбираются из БД — запрос дешевле и быстрее.","schema":{"example":"id,name,price_min,currency","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Сортировка: relevance — по релевантности поиска (только вместе с q; при заданном q это порядок по умолчанию, без q игнорируется); price/-price — по мин. цене активных офферов (товары без цены — в конце); updated_at/-updated_at — по времени изменения (поток изменений для sync); created_at/-created_at — по времени добавления (-created_at = новинки сверху); popular — по курируемому рангу популярности (меньше = популярнее, не прокурированные — в конце). По умолчанию — по стабильному id.","schema":{"type":"string","enum":["relevance","price","-price","updated_at","-updated_at","created_at","-created_at","popular"]}},{"name":"with_total","required":false,"in":"query","description":"Вернуть best-effort `total` в page-meta (доп. count по фильтру). По умолчанию не считается (экономия на горячем пути).","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicFacetsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Фасеты режима витрины «Коллекции»","tags":["public"]}},"/api/public/v1/collections/{idOrSlug}":{"get":{"description":"Коллекция по id или slug: имя, интерьеры и бренд. Slug коллекции уникален только внутри бренда, поэтому SEO-URL витрины — пара «бренд + коллекция»: /collections/:slug?brand=<slug бренда>. Коллекция без единого публичного товара наружу не идёт.","operationId":"PublicCatalogController_collection","parameters":[{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"brand","required":false,"in":"query","description":"Slug бренда — снимает неоднозначность slug коллекции (он уникален только внутри бренда). С ним :idOrSlug трактуется как slug коллекции этого бренда.","schema":{"example":"cifre","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCollectionEntity"}}}},"404":{"description":"Коллекция не найдена."}},"security":[{"bearer":[]}],"summary":"Карточка коллекции","tags":["public"]}},"/api/public/v1/collections/{brandSlug}/{slug}/resolve":{"get":{"description":"Резолвит slug коллекции — текущий или устаревший (алиас после переименования курацией, `CollectionSlugAlias`) — в канонический, для 301-редиректа со старого адреса `/collections/<бренд>/<коллекция>`. Неизвестная пара/коллекция без публичного товара — 404.","operationId":"PublicCatalogController_resolveCollection","parameters":[{"name":"brandSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryResolveEntity"}}}},"404":{"description":"Коллекция не найдена."}},"security":[{"bearer":[]}],"summary":"Канонический slug коллекции (brand-scoped)","tags":["public"]}},"/api/public/v1/stores":{"get":{"description":"Физические точки хранения/продажи с гео-координатами, адресом и фото. Отдаются только активные точки публичных поставщиков, у которых есть хоть один публичный остаток (см. /store-items). Остатки на офферах поставщиков — отдельное измерение (карточка товара / offers). Наполняется по мере подключения складских фидов поставщиков — сейчас ни один фид складов не отдаёт, ручка стабильно отвечает пустым списком.","operationId":"PublicCatalogController_stores","parameters":[{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedStoresEntity"}}}}},"security":[{"bearer":[]}],"summary":"Склады и магазины каталога","tags":["public"]}},"/api/public/v1/store-items":{"get":{"description":"Остаток и отпускная цена товара на конкретном складе. Опц. фильтры: store_id (остатки одной точки) и product_id (все точки товара). Наружу — только отпускная цена (price); точный остаток (qty) — под scope stock:read дилерского ключа, иначе null. Наличие (in_stock) — дериват остатка, доступно всегда. Наполняется по мере подключения складских фидов поставщиков — сейчас ни один фид складов не отдаёт, ручка стабильно отвечает пустым списком.","operationId":"PublicCatalogController_storeItems","parameters":[{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"store_id","required":false,"in":"query","description":"Только остатки этого склада (id из GET /stores).","schema":{"example":"clx0store0001","type":"string"}},{"name":"product_id","required":false,"in":"query","description":"Только остатки этого товара. Принимает id, public_id (в т.ч. выбывший после склейки) или slug — тот же ключ, что GET /products/{id}/offers.","schema":{"example":"clx0prod0001","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedStoreItemsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Остатки товаров по складам","tags":["public"]}},"/api/public/v1/changes":{"get":{"description":"Одним запросом вместо ручного сравнения полного каталога: новые товары, изменения (цена/наличие/др.) и уходы из публичного среза (снят с публикации/потерял качество/ поставщик закрыл выдачу). `since` обязателен (ISO 8601) — обычно это `as_of` предыдущего ответа этой же ручки; для первичной заливки используйте GET /products/export, дальше синхронизируйтесь отсюда. `new`/`updated` пагинируются курсором (`page.next_cursor`), `removed` — страницами по 500 (`removed_cursor`). Курсоры помнят окно прохода: `as_of` одинаков на всех страницах, `since` при пагинации не меняйте, а на `as_of` двигайтесь, только выбрав проход до конца.","operationId":"PublicChangesController_getChanges","parameters":[{"name":"cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы (из page.next_cursor).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Размер страницы.","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"since","required":true,"in":"query","description":"Момент последней синхронизации (ISO 8601) — обычно `as_of` из предыдущего ответа этой же ручки. Для первой синхронизации используйте GET /products/export, затем продолжайте отсюда. При пагинации передавайте ТОТ ЖЕ `since`, что и на первой странице прохода: курсор помнит окно, чужой `since` отбивается 400.","schema":{"example":"2026-07-01T00:00:00Z","type":"string"}},{"name":"removed_cursor","required":false,"in":"query","description":"Opaque-курсор следующей страницы удалений (из `removed_cursor` предыдущего ответа). Независим от `cursor`: листает только `removed`.","schema":{"type":"string"}},{"name":"segment","required":false,"in":"query","description":"Применить сохранённый срез («мой ассортимент») по id: сервер разворачивает его в свой набор фильтров вместо длинной строки запроса. Срез создаётся в кабинете («Мои срезы»), там же видно его id. Явные параметры запроса перекрывают срез: `?segment=<id>&in_stock=true` — срез плюс сужение. Чужой/несуществующий id — 404.","schema":{"example":"clz1segment0000example","type":"string"}},{"name":"matched_only","required":false,"in":"query","description":"Только товары, сопоставленные с моей номенклатурой (загрузка прайса в кабинете, строки со статусом «сопоставлено автоматически» или «подтверждено»). Сужает выдачу до реального ассортимента дилера: и листинг, и дельты `/changes` (включая раздел `removed`), и качество среза в кабинете. Номенклатура не загружена или ни одна строка не сопоставлена — выдача пуста (это ответ «в вашем срезе ничего нет», а не ошибка).","schema":{"example":true,"type":"boolean"}},{"name":"only","required":false,"in":"query","description":"Ограничить ответ одним потоком: `main` — только `new`+`updated` (без `removed`), `removed` — только уходы (без листинга товаров). Без параметра приходят оба потока. Полезно при раздельной пагинации: не тянуть второй поток ради его отбрасывания.","schema":{"example":"removed","type":"string","enum":["main","removed"]}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicChangesEntity"}}}}},"security":[{"bearer":[]}],"summary":"Диф-лента: что изменилось в срезе клиента с момента since","tags":["public"]}},"/api/public/v1/webhooks":{"get":{"description":"Список приёмников клиента с их здоровьем (последний успех/провал, причина, сколько неудач подряд). Секрет подписи не отдаётся: он показывается один раз при создании.","operationId":"PublicWebhookController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscriptionEntity"}}}}}},"security":[{"bearer":[]}],"summary":"Мои подписки на вебхуки","tags":["public"]},"post":{"description":"Каждая доставка — POST с заголовками X-Webhook-Event, X-Webhook-Delivery, X-Webhook-Timestamp, X-Webhook-Attempt и подписью X-Webhook-Signature: `sha256=<hex HMAC-SHA256(секрет, \"<timestamp>.<тело>\")>`. Проверяйте подпись и свежесть метки времени — иначе приёмник примет чужой прайс. Ответ 2xx = принято; на любой другой ответ или таймаут мы повторим доставку с растущей паузой (до 6 попыток, от минуты до 6 часов). Тело события — окно изменений (`since`/`as_of` + счётчик и готовая ссылка `changes_url`); сами изменения забирайте из GET /changes: там ваш срез, дельты «было → стало» и удаления.","operationId":"PublicWebhookController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhookSubscriptionEntity"}}}},"409":{"description":"Адрес уже подписан либо упёрлись в потолок подписок."},"503":{"description":"Канал вебхуков на стенде не настроен."}},"security":[{"bearer":[]}],"summary":"Подписаться на изменения цен/остатков (push вместо поллинга)","tags":["public"]}},"/api/public/v1/webhooks/{id}":{"patch":{"description":"Пауза через `is_active: false` вместо удаления: секрет и история доставок остаются.","operationId":"PublicWebhookController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionEntity"}}}},"404":{"description":"Подписка не найдена."},"409":{"description":"Адрес уже подписан."}},"security":[{"bearer":[]}],"summary":"Изменить подписку (адрес, события, пауза)","tags":["public"]},"delete":{"operationId":"PublicWebhookController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""},"404":{"description":"Подписка не найдена."}},"security":[{"bearer":[]}],"summary":"Удалить подписку","tags":["public"]}},"/api/public/v1/webhooks/{id}/rotate-secret":{"post":{"description":"Новый секрет показывается один раз, старый перестаёт работать СРАЗУ — обновите его на приёмнике. Не хотите терять доставки на время замены — сперва поставьте подписку на паузу (`is_active: false`): очередь скопится и уедет после снятия.","operationId":"PublicWebhookController_rotateSecret","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotatedWebhookSecretEntity"}}}},"404":{"description":"Подписка не найдена."},"503":{"description":"Канал вебхуков на стенде не настроен."}},"security":[{"bearer":[]}],"summary":"Сменить секрет подписи","tags":["public"]}},"/api/public/v1/webhooks/{id}/ping":{"post":{"description":"Отправляет событие `ping` тем же конвертом и с той же подписью, что боевые доставки, и возвращает ответ приёмника — проверка обработчика при первом подключении. Паузу подписки не учитывает; в ленту доставок не попадает и здоровье приёмника не меняет.","operationId":"PublicWebhookController_ping","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPingEntity"}}}},"404":{"description":"Подписка не найдена."},"503":{"description":"Канал вебхуков на стенде не настроен."}},"security":[{"bearer":[]}],"summary":"Тестовая доставка на приёмник","tags":["public"]}},"/api/public/v1/webhooks/{id}/deliveries":{"get":{"description":"Отладка интеграции без похода в поддержку: что мы отправляли, чем ответил приёмник, сколько было попыток и когда будет следующая.","operationId":"PublicWebhookController_deliveries","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryEntity"}}}}},"404":{"description":"Подписка не найдена."}},"security":[{"bearer":[]}],"summary":"Последние доставки подписки","tags":["public"]}},"/api/public/v1/menu":{"get":{"description":"Готовая навигация одним запросом: то же дерево разделов, что у /categories, плюс курируемые подкатегории-лендинги каждого раздела (presets — slug, имя, счётчик). Ровно то, из чего строится мега-меню витрины: без этой ручки клиент добирал бы пресеты запросом на КАЖДЫЙ раздел. Пустые разделы и пустые пресеты не отдаются, число пресетов в узле ограничено верхушкой курации — полный список пресетов раздела (SEO-тексты + залоченный фильтр) берётся в /categories/:categorySlug/presets. Пагинации нет: меню приезжает целиком.","operationId":"PublicNavigationController_menu","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicMenuEntity"}}}}},"security":[{"bearer":[]}],"summary":"Меню каталога: разделы вместе с подкатегориями-лендингами","tags":["public"]}},"/api/public/v1/categories":{"get":{"description":"Разделы витрины (Керамогранит / Мозаика / Клинкер / …) деревом, со счётчиками товаров: счётчик узла включает его подразделы. slug узла подставляется в фильтр category у /products — тот тоже забирает подразделы. Отдаётся целиком, без пагинации: дерево рассчитано на один запрос под рендер меню. Не путать с характеристикой material — она описывает состав товара, а не раздел каталога.","operationId":"PublicNavigationController_categories","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoriesEntity"}}}}},"security":[{"bearer":[]}],"summary":"Дерево разделов каталога","tags":["public"]}},"/api/public/v1/categories/{slug}/resolve":{"get":{"description":"Резолвит slug раздела (текущий или устаревший алиас после переименования курацией) в канонический — для 301-редиректа со старой страницы /catalog/razdel/<slug>. Неизвестный slug или скрытый/удалённый раздел — 404.","operationId":"PublicNavigationController_resolveCategory","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryResolveEntity"}}}},"404":{"description":"Раздел не найден."}},"security":[{"bearer":[]}],"summary":"Канонический slug раздела","tags":["public"]}},"/api/public/v1/categories/{categorySlug}/facet-groups":{"get":{"description":"Курируемый состав осей фильтра раздела в порядке показа: чем сужают срез у керамогранита (Рисунок/Формат/Поверхность) и чем — у плитки (Помещение/Назначение/Цвет). Подраздел без своего состава наследует ближайший раздел-предок. Каждая ось несёт type (list / range / bool) — контрол фильтра и способ его передать, чтобы виджет не приходилось выводить из ключа. Значения и счётчики осей — в /products/facets по тому же срезу (key совпадает с осью фасетов), здесь только состав. Оси без данных в разделе не отдаются. Неизвестный раздел (как и раздел без состава по всей ветке) — пустой список.","operationId":"PublicNavigationController_categoryFacetGroups","parameters":[{"name":"categorySlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryFacetGroupsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Фасетные группы раздела (состав панели фильтров)","tags":["public"]}},"/api/public/v1/categories/{categorySlug}/presets":{"get":{"description":"Курируемые срезы фильтра внутри раздела («Керамогранит под дерево») — адресуемые страницы /catalog/<category>/<preset>. Каждый пресет несёт залоченный фильтр по характеристикам (attr) и счётчик товаров (раздел + фильтр). Пустые пресеты не отдаются. Неизвестный раздел — пустой список.","operationId":"PublicNavigationController_categoryPresets","parameters":[{"name":"categorySlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryPresetsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Пресеты фасетов раздела (подкатегории-лендинги)","tags":["public"]}},"/api/public/v1/categories/{categorySlug}/presets/{slug}/resolve":{"get":{"description":"Резолвит slug пресета (текущий или устаревший алиас после переименования курацией) в канонический — для 301-редиректа со старого адреса /catalog/<category>/<preset>. Неизвестный slug, снятый или опустевший пресет — 404.","operationId":"PublicNavigationController_resolveCategoryPreset","parameters":[{"name":"categorySlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryResolveEntity"}}}},"404":{"description":"Пресет не найден."}},"security":[{"bearer":[]}],"summary":"Канонический slug пресета раздела","tags":["public"]}},"/api/public/v1/demo/stats":{"get":{"description":"Масштаб каталога (товары/бренды/коллекции/разделы/поставщики + момент последнего обновления), крупнейшие бренды и коллекции по именам со счётчиками и описание демо-среза текущего ключа. Товаров здесь нет: ручка показывает форму и объём данных, а не сами данные.","operationId":"PublicDemoController_stats","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDemoStatsEntity"}}}}},"security":[{"bearer":[]}],"summary":"Агрегаты каталога для песочницы","tags":["public"]}}},"info":{"title":"Best API — публичный API","description":"Публичный API товарных данных Best API (read-only). Авторизация — API-ключ в заголовке `Authorization: Bearer <key>`. Пагинация — cursor. Ошибки — RFC 9457.\n\n**Версии и совместимость.** Версия — сегмент пути (`/public/v1`). Внутри версии изменения только аддитивные: новые ручки, новые необязательные параметры, новые поля ответа и новые значения перечислений — незнакомое клиент обязан игнорировать. Ломающее изменение выходит новым сегментом версии.\n\n**Депрекация.** Устаревшая ручка (или поле её ответа) отвечает как обычно, но несёт заголовки `Deprecation` (RFC 9745) и `Sunset` (RFC 8594) с датой отключения, `Link; rel=\"deprecation\"` на политику и `rel=\"successor-version\"` на замену; в спеке операция помечена `deprecated` и расширением `x-deprecation`. Между объявлением и отключением — не меньше 180 дней. Политика целиком, лимиты и SLA — на странице `/developers`.","version":"v1","contact":{}},"tags":[{"name":"public","description":"Каталог (read-only)"}],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"API-ключ (bapi_live_a1b2c3… / bapi_test_a1b2c3…)."}},"schemas":{"PublicProfileEntity":{"type":"object","properties":{"client_id":{"type":"string","example":"dev-client"},"name":{"type":"string","example":"Dev Client"},"scopes":{"example":["catalog:read","stock:read"],"type":"array","items":{"type":"string"}},"vertical":{"type":"string","example":"tile","description":"Продукт (вертикаль), по которому ключ отдаёт данные."}},"required":["client_id","name","scopes","vertical"]},"PublicPolicyRateLimitEntity":{"type":"object","properties":{"limit":{"type":"number","example":600,"description":"Эффективный лимит ЭТОГО ключа: запросов в окно."},"window_s":{"type":"number","example":60,"description":"Окно лимита, секунд."}},"required":["limit","window_s"]},"PublicPolicyCacheEntity":{"type":"object","properties":{"max_age_s":{"type":"number","example":60,"description":"Сколько ответ разрешено переиспользовать клиенту, секунд."},"shared_max_age_s":{"type":"number","example":600,"description":"Сколько ответ держит общий кэш (nginx/CDN), секунд."},"stale_while_revalidate_s":{"type":"number","example":300,"description":"Окно отдачи устаревшего ответа на фоне обновления, секунд."}},"required":["max_age_s","shared_max_age_s","stale_while_revalidate_s"]},"PublicPolicyDeprecationEntity":{"type":"object","properties":{"min_notice_days":{"type":"number","example":180,"description":"Минимум дней между объявлением депрекации и отключением. Проверяется на старте API."},"policy_url":{"type":"string","example":"/developers#api-policy","description":"Адрес политики — туда же ведёт Link; rel=\"deprecation\"."}},"required":["min_notice_days","policy_url"]},"PublicPolicySurfaceEntity":{"type":"object","properties":{"surface":{"type":"string","example":"dealer","description":"Поверхность ключа: dealer (интеграция) или storefront (витрина)."},"rate_limit_per_minute":{"type":"number","example":600},"max_page_size":{"type":"number","example":200,"description":"Потолок размера страницы (параметр limit)."}},"required":["surface","rate_limit_per_minute","max_page_size"]},"PublicPolicyEntity":{"type":"object","properties":{"version":{"type":"string","example":"v1","description":"Версия контракта — сегмент пути."},"surface":{"type":"string","example":"dealer","description":"Поверхность ЭТОГО ключа."},"rate_limit":{"$ref":"#/components/schemas/PublicPolicyRateLimitEntity"},"max_page_size":{"type":"number","example":200,"description":"Потолок размера страницы для этого ключа."},"cache":{"$ref":"#/components/schemas/PublicPolicyCacheEntity"},"deprecation":{"$ref":"#/components/schemas/PublicPolicyDeprecationEntity"},"surfaces":{"description":"Объявленные значения по поверхностям — то же, что публикует страница /developers.","type":"array","items":{"$ref":"#/components/schemas/PublicPolicySurfaceEntity"}}},"required":["version","surface","rate_limit","max_page_size","cache","deprecation","surfaces"]},"PublicPropertyValueEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0val0001"},"slug":{"type":"string","example":"bezhevyy"},"name":{"type":"string","example":"Бежевый"}},"required":["id","slug","name"]},"PublicPropertyEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0attr0001"},"key":{"type":"string","example":"color"},"name":{"type":"string","example":"Цвет"},"type":{"type":"string","enum":["list","number","bool","string"],"example":"list"},"unit":{"type":"string","nullable":true,"example":"кг"},"is_multi":{"type":"boolean","example":false,"description":"Множественный выбор значения."},"is_filterable":{"type":"boolean","example":true},"values":{"description":"Значения для type=list; для прочих типов пусто.","type":"array","items":{"$ref":"#/components/schemas/PublicPropertyValueEntity"}}},"required":["id","key","name","type","unit","is_multi","is_filterable","values"]},"PageMetaEntity":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true,"description":"Курсор следующей страницы; null — записей больше нет.","example":"Y2x4MGJyYW5kMDAwMQ"},"has_more":{"type":"boolean","example":true,"description":"Есть ли ещё страницы."},"total":{"type":"number","description":"Всего записей по фильтру (best-effort). Присутствует только при `with_total=true`.","example":137}},"required":["next_cursor","has_more"]},"PaginatedPropertiesEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicPropertyEntity"}},"page":{"$ref":"#/components/schemas/PageMetaEntity"}},"required":["data","page"]},"PublicCountryEntity":{"type":"object","properties":{"code":{"type":"string","example":"ES","description":"ISO 3166-1 alpha-2."},"alpha3":{"type":"string","example":"ESP","description":"ISO 3166-1 alpha-3."},"numeric":{"type":"string","example":"724","description":"ISO 3166-1 цифровой код."},"name":{"type":"string","example":"Испания"},"phone_code":{"type":"string","example":"+34","description":"Телефонный код."},"domain":{"type":"string","example":"es","description":"Национальный домен (ccTLD)."}},"required":["code","alpha3","numeric","name","phone_code","domain"]},"PublicCountriesEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCountryEntity"}}},"required":["data"]},"PublicMeasureEntity":{"type":"object","properties":{"code":{"type":"string","example":"055","description":"Код ОКЕИ."},"name":{"type":"string","example":"Квадратный метр"},"symbol":{"type":"string","example":"м2","description":"Условное обозначение."},"unit":{"type":"string","nullable":true,"example":"m2","description":"Значение поля unit товара, если единица используется в каталоге."}},"required":["code","name","symbol","unit"]},"PublicMeasuresEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicMeasureEntity"}}},"required":["data"]},"PublicProductCategoryEntity":{"type":"object","properties":{"slug":{"type":"string","example":"keramogranit"},"name":{"type":"string","example":"Керамогранит"}},"required":["slug","name"]},"PublicPriceBandEntity":{"type":"object","properties":{"from":{"type":"number","example":1700,"description":"Нижняя граница диапазона, руб."},"to":{"type":"number","example":1800,"description":"Верхняя граница диапазона (не включительно), руб."}},"required":["from","to"]},"PublicImageVariantEntity":{"type":"object","properties":{"width":{"type":"number","example":640},"url":{"type":"string","example":"https://cdn.example.com/imgproxy/insecure/rs:fill:640:0/q:80/<b64>"}},"required":["width","url"]},"PublicImageEntity":{"type":"object","properties":{"src":{"type":"string","description":"Оригинал: абсолютный URL (картинки поставщиков — как есть, наши ассеты — от публичного адреса сайта PUBLIC_SITE_URL).","example":"https://cdn.supplier.example/products/a.jpg"},"srcset":{"description":"Варианты по ширинам (320/640/960/1280) через imgproxy, формат — webp/avif по Accept. Пустой массив — публичная база imgproxy не сконфигурирована (IMGPROXY_PUBLIC_BASE), тогда доступен только src.","type":"array","items":{"$ref":"#/components/schemas/PublicImageVariantEntity"}},"lqip":{"type":"string","nullable":true,"description":"Крошечный размытый плейсхолдер (LQIP) под кроссфейд/резерв места — против CLS. null — база imgproxy не сконфигурирована.","example":"https://cdn.example.com/imgproxy/insecure/rs:fill:32:0/q:30/bl:4/<b64>"}},"required":["src","srcset","lqip"]},"PublicProductPropertyEntity":{"type":"object","properties":{"key":{"type":"string","example":"color"},"name":{"type":"string","example":"Цвет"},"value":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}],"example":"Бежевый"}},"required":["key","name","value"]},"PublicPriceDeltaEntity":{"type":"object","properties":{"previous":{"type":"number","example":500,"description":"price_from на момент `since` (руб.)."},"current":{"type":"number","example":600,"description":"Текущий price_from (руб.) — совпадает с полем price_from товара."}},"required":["previous","current"]},"PublicStockDeltaEntity":{"type":"object","properties":{"previous":{"type":"boolean","example":true,"description":"in_stock на момент `since`."},"current":{"type":"boolean","example":false,"description":"Текущий in_stock — совпадает с полем in_stock товара."}},"required":["previous","current"]},"PublicProductDeltaEntity":{"type":"object","properties":{"price":{"nullable":true,"description":"Изменение минимальной цены с момента `since`. null — цена не менялась либо историю цен на момент `since` восстановить не удалось (оффер появился позже).","type":"object","allOf":[{"$ref":"#/components/schemas/PublicPriceDeltaEntity"}]},"stock":{"nullable":true,"description":"Изменение наличия с момента `since` («был в наличии → нет»). null — наличие не менялось либо историю наличия на момент `since` восстановить не удалось (оффер появился позже).","type":"object","allOf":[{"$ref":"#/components/schemas/PublicStockDeltaEntity"}]}},"required":["price","stock"]},"PublicProductVariantEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0prod0002"},"public_id":{"type":"string","example":"clx0pub0002","description":"Неизменный public_id варианта — переживает склейку дублей, в отличие от id."},"slug":{"type":"string","example":"cifre-alchimia-decor-9x9-grey"},"name":{"type":"string","example":"Cifre Alchimia Decor 9x9 Grey"},"format":{"type":"string","nullable":true,"example":"60x120","description":"Формат варианта (дериват размеров) — ось различия «формат». null — размеров нет."},"color":{"type":"string","nullable":true,"example":"Серый","description":"Значение offer-property характеристики color — ось различия «цвет» (свотч). null — у варианта нет цвета."},"in_stock":{"type":"boolean","example":true},"image":{"nullable":true,"description":"Главное изображение варианта (srcset + LQIP). null — картинки нет.","type":"object","allOf":[{"$ref":"#/components/schemas/PublicImageEntity"}]}},"required":["id","public_id","slug","name","format","color","in_stock","image"]},"PublicProductVariantsSummaryEntity":{"type":"object","properties":{"total":{"type":"number","example":6,"description":"Всего вариантов в модели (публичных, вкл. текущий). Источник «+N» на карточке."},"colors":{"example":["Серый","Бежевый"],"description":"Различающиеся цвета вариантов (offer-property color) для свотчей — без повторов, капнуто. Точное «сколько ещё» несёт total, а не длина списка.","type":"array","items":{"type":"string"}},"formats":{"example":["60x60","60x120"],"description":"Различающиеся форматы вариантов (дериват размеров) для чипов — без повторов, капнуто.","type":"array","items":{"type":"string"}}},"required":["total","colors","formats"]},"PublicProductEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0prod0001"},"public_id":{"type":"string","example":"clx0pub0001","description":"Неизменный внешний id карточки: присваивается при создании и не меняется никогда. Именно на него стоит вешать свою номенклатуру: `id`/`slug` внутренние и при склейке дублей могут смениться, а `public_id` склейку переживает — GET /products/{public_id} и после неё отдаёт актуальную карточку."},"slug":{"type":"string","example":"cifre-alchimia-decor-9x9"},"name":{"type":"string","example":"Cifre Alchimia Decor 9x9"},"brand_sku":{"type":"string","nullable":true,"example":"AL-9X9-GR","description":"Артикул производителя (аналог GPD article/brand_sku). null — фид его не даёт либо ещё не проставлен курацией."},"gtin":{"type":"string","nullable":true,"example":null,"description":"GTIN/штрихкод — задел под сильный кросс-поставщицкий идентификатор (T1-матчинг). В подключённых фидах пока не встречается, всегда null."},"kind":{"type":"string","enum":["tile","accessory"],"example":"tile"},"brand_id":{"type":"string","nullable":true,"example":"clx0brand0001"},"collection_id":{"type":"string","nullable":true,"example":"clx0coll0001"},"category":{"nullable":true,"description":"Раздел каталога товара (slug — ключ фильтра category у /products). null — раздел не выведен: фид не дал материала либо материал описывает состав, а не раздел. Не путать с характеристикой material.","type":"object","allOf":[{"$ref":"#/components/schemas/PublicProductCategoryEntity"}]},"width_cm":{"type":"number","nullable":true,"example":9},"length_cm":{"type":"number","nullable":true,"example":9},"thickness_mm":{"type":"number","nullable":true,"example":8},"format":{"type":"string","nullable":true,"example":"60x120","description":"Формат — дериват width_cm×length_cm (меньшая сторона первой). null — если размеры не заданы. Значение — ключ фильтра format."},"size_category":{"type":"string","nullable":true,"enum":["small","medium","large","grand"],"example":"large","description":"Категория размера — бакет по большей стороне: small (<30 см), medium (30–60), large (60–120), grand (от 120). null — если размеры не заданы. Значение — ключ фильтра size_category."},"unit":{"type":"string","example":"m2","description":"Единица: m2/piece/running_meter/pack."},"measure_code":{"type":"string","nullable":true,"example":"055","description":"Код единицы измерения по ОКЕИ (ОК 015-94), выведен из unit. Интегратору с GPD-совместимой моделью — тот же справочник без словаря. null — у единицы нет кода ОКЕИ."},"placement":{"type":"string","nullable":true,"example":"floor"},"in_stock":{"type":"boolean","example":true},"price_from":{"type":"number","nullable":true,"description":"Минимальная цена по активным офферам, руб. Алиас price_min (совместимость). null в листинге витринного ключа — там цена приходит диапазоном (price_band).","example":1490},"price_min":{"type":"number","nullable":true,"description":"Минимальная цена по активным офферам поставщиков, руб. Считается только по офферам в единице товара (поле unit): офферы в другой единице в диапазон не идут.","example":1490},"price_max":{"type":"number","nullable":true,"description":"Максимальная цена по активным офферам поставщиков, руб. Единица — как у price_min. null у витринного ключа: разброс цен — структура предложения, а не карточка.","example":1720},"price_band":{"nullable":true,"description":"Ценовой диапазон, в который попадает минимальная цена. Отдаётся вместо точной цены в листинге витринного ключа (у дилерского всегда null — там точная цена).","type":"object","allOf":[{"$ref":"#/components/schemas/PublicPriceBandEntity"}]},"old_price":{"type":"number","nullable":true,"example":2400,"description":"Старая цена (до скидки), руб. — для зачёркнутой цены рядом с price_from. null — скидки нет, либо поверхность её не отдаёт (та же граница, что у price_min: null в листинге витринного ключа, где цена приходит диапазоном price_band)."},"discount_percent":{"type":"number","nullable":true,"example":30,"description":"Скидка бейджа «-N%», целое число процентов. null — скидки нет (см. old_price) либо она не наберётся из старой и текущей цены (старая цена не выше текущей)."},"currency":{"type":"string","nullable":true,"description":"Валюта диапазона цен (обычно все офферы одной валюты). null — цен нет.","example":"RUB"},"popularity_rank":{"type":"number","nullable":true,"example":1,"description":"Курируемый ранг популярности (сортировка sort=popular) — меньше значит популярнее. null — курации нет (сигнала просмотров/продаж в модели нет)."},"offers_count":{"type":"number","nullable":true,"description":"Число активных офферов по товару. null у витринного ключа.","example":3},"suppliers_count":{"type":"number","nullable":true,"description":"Число поставщиков с активным оффером. null у витринного ключа.","example":2},"total_qty":{"type":"number","nullable":true,"description":"Сумма ИЗВЕСТНЫХ остатков по офферам в единице товара (null — ни один фид не даёт числа, либо у ключа нет scope stock:read). Подсказка: возможен двойной счёт одного склада разными поставщиками — не гарантированный факт.","example":540},"image":{"nullable":true,"description":"Главное изображение: оригинал + srcset + LQIP. null — картинки нет.","type":"object","allOf":[{"$ref":"#/components/schemas/PublicImageEntity"}]},"images":{"description":"Галерея: остальные изображения товара, каждое — с srcset и LQIP.","type":"array","items":{"$ref":"#/components/schemas/PublicImageEntity"}},"properties":{"type":"array","items":{"$ref":"#/components/schemas/PublicProductPropertyEntity"}},"delta":{"description":"Величина изменения относительно `since` (было → стало). Отдаётся ТОЛЬКО в корзине `updated` дифф-ленты /changes — в листинге, карточке и `new` поля нет.","allOf":[{"$ref":"#/components/schemas/PublicProductDeltaEntity"}]},"variants":{"description":"Варианты той же модели (этот же товар в других цветах/форматах), без текущего. Отдаётся ТОЛЬКО в карточке товара (GET /products/:idOrSlug) — в листинге поля нет. Пустой массив — у товара нет вариантов.","type":"array","items":{"$ref":"#/components/schemas/PublicProductVariantEntity"}},"variants_summary":{"description":"Компактная сводка вариантов модели для карточки в гриде (свотчи цветов + чипы форматов + всего). Отдаётся ТОЛЬКО в листинге (GET /products) — в карточке товара вместо неё полный variants. Поля нет у товара без вариантов (меньше двух).","allOf":[{"$ref":"#/components/schemas/PublicProductVariantsSummaryEntity"}]},"created_at":{"type":"string","example":"2026-02-28T10:12:13.000Z","description":"Момент добавления товара в каталог (лента новинок)."},"updated_at":{"type":"string","example":"2026-02-28T10:12:13.000Z"}},"required":["id","public_id","slug","name","brand_sku","gtin","kind","brand_id","collection_id","category","width_cm","length_cm","thickness_mm","format","size_category","unit","measure_code","placement","in_stock","price_from","price_min","price_max","price_band","old_price","discount_percent","currency","popularity_rank","offers_count","suppliers_count","total_qty","image","images","properties","created_at","updated_at"]},"PaginatedProductsEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicProductEntity"}},"page":{"$ref":"#/components/schemas/PageMetaEntity"}},"required":["data","page"]},"FacetEntityBucket":{"type":"object","properties":{"id":{"type":"string","example":"clx0brand0001"},"name":{"type":"string","example":"Cifre"},"count":{"type":"number","example":42}},"required":["id","name","count"]},"FacetCategoryBucket":{"type":"object","properties":{"slug":{"type":"string","example":"keramogranit"},"name":{"type":"string","example":"Керамогранит"},"count":{"type":"number","example":4210}},"required":["slug","name","count"]},"FacetValueBucket":{"type":"object","properties":{"value":{"type":"string","example":"tile"},"count":{"type":"number","example":120}},"required":["value","count"]},"FacetNamedBucket":{"type":"object","properties":{"value":{"type":"string","example":"large"},"name":{"type":"string","example":"Крупный"},"count":{"type":"number","example":64}},"required":["value","name","count"]},"FacetBoolBucket":{"type":"object","properties":{"value":{"type":"boolean","example":true},"count":{"type":"number","example":98}},"required":["value","count"]},"FacetPriceRange":{"type":"object","properties":{"min":{"type":"number","nullable":true,"example":450},"max":{"type":"number","nullable":true,"example":9800}},"required":["min","max"]},"FacetAttrValueBucket":{"type":"object","properties":{"id":{"type":"string","example":"clx0val0001"},"slug":{"type":"string","example":"bezhevyi"},"name":{"type":"string","example":"Бежевый"},"count":{"type":"number","example":17}},"required":["id","slug","name","count"]},"FacetAttribute":{"type":"object","properties":{"key":{"type":"string","example":"color"},"name":{"type":"string","example":"Цвет"},"values":{"type":"array","items":{"$ref":"#/components/schemas/FacetAttrValueBucket"}}},"required":["key","name","values"]},"PublicFacetsEntity":{"type":"object","properties":{"brands":{"type":"array","items":{"$ref":"#/components/schemas/FacetEntityBucket"}},"collections":{"type":"array","items":{"$ref":"#/components/schemas/FacetEntityBucket"}},"categories":{"description":"Разделы каталога в текущей выборке. Счётчик — товары самого раздела (в отличие от /categories, где счётчик узла включает подразделы). slug подставляется в фильтр category.","type":"array","items":{"$ref":"#/components/schemas/FacetCategoryBucket"}},"kinds":{"type":"array","items":{"$ref":"#/components/schemas/FacetValueBucket"}},"placements":{"type":"array","items":{"$ref":"#/components/schemas/FacetValueBucket"}},"formats":{"description":"Форматы — дериват width_cm×length_cm («60x120», ориентация схлопнута) со счётчиками. value подставляется в фильтр format.","type":"array","items":{"$ref":"#/components/schemas/FacetValueBucket"}},"size_categories":{"description":"Категории размера — бакеты по большей стороне (small/medium/large/grand) со счётчиками. Порядок — от мелкого к гранду (ось монотонна, не по счётчику). value подставляется в фильтр size_category.","type":"array","items":{"$ref":"#/components/schemas/FacetNamedBucket"}},"in_stock":{"type":"array","items":{"$ref":"#/components/schemas/FacetBoolBucket"}},"price":{"$ref":"#/components/schemas/FacetPriceRange"},"attributes":{"type":"array","items":{"$ref":"#/components/schemas/FacetAttribute"}}},"required":["brands","collections","categories","kinds","placements","formats","size_categories","in_stock","price","attributes"]},"PublicOfferEntity":{"type":"object","properties":{"supplier_id":{"type":"string","example":"clx0supp0001"},"supplier_name":{"type":"string","example":"Керамир"},"price":{"type":"number","nullable":true,"example":1490},"currency":{"type":"string","example":"RUB"},"in_stock":{"type":"boolean","nullable":true,"description":"Наличие у поставщика: true/false/null (фид не сообщает).","example":true},"qty":{"type":"number","nullable":true,"description":"Остаток (шт/м²), если фид даёт и у ключа есть scope stock:read.","example":320},"measure_ratio":{"type":"number","nullable":true,"description":"Кратность продажи (аналог GPD measure_ratio): для плитки — площадь упаковки, без которой дилер не соберёт корзину кратно коробкам. null — фид площадь упаковки не дал.","example":1.44},"measure_ratio_min":{"type":"number","nullable":true,"description":"Минимальная партия продажи оффера (аналог GPD measure_ratio_min). Сейчас всегда равна measure_ratio (явного минимума фиды не отдают отдельно от шага упаковки).","example":1.44},"updated_at":{"type":"string","example":"2026-02-28T10:12:13.000Z"}},"required":["supplier_id","supplier_name","price","currency","in_stock","qty","measure_ratio","measure_ratio_min","updated_at"]},"ProductOffersEntity":{"type":"object","properties":{"product_id":{"type":"string","example":"clx0prod0001"},"offers":{"type":"array","items":{"$ref":"#/components/schemas/PublicOfferEntity"}}},"required":["product_id","offers"]},"ProductIdsEntity":{"type":"object","properties":{"ids":{"example":["clx0pub0001","clx0pub0002"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"PublicBrandSupplierEntity":{"type":"object","properties":{"slug":{"type":"string","example":"fiesta-ceramica"},"name":{"type":"string","example":"Фиеста Керамика"}},"required":["slug","name"]},"PublicBrandCategoryEntity":{"type":"object","properties":{"slug":{"type":"string","example":"keramogranit"},"name":{"type":"string","example":"Керамогранит"},"products_count":{"type":"number","example":42}},"required":["slug","name","products_count"]},"PublicBrandEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0brand0001"},"slug":{"type":"string","example":"cifre"},"name":{"type":"string","example":"Cifre"},"country":{"type":"string","nullable":true,"example":"Испания","description":"Имя страны (из справочника; для ненормализованных — сырой текст фида)."},"country_code":{"type":"string","nullable":true,"example":"ES","description":"Страна ISO 3166-1 alpha-2. Стабильный ключ фильтра country. null — не нормализована."},"country_slug":{"type":"string","nullable":true,"example":"ispaniya","description":"Человекочитаемый ключ страны (транслит имени) — для SEO-URL витрины. null — страна не нормализована."},"is_own_brand":{"type":"boolean","example":false,"description":"Своя марка поставщика (own-brand), а не реальная марка."},"description":{"type":"string","nullable":true,"description":"Описание бренда (курация оператора). null — не заполнено.","example":"Cifre — испанский производитель керамогранита с 1968 года."},"logo":{"nullable":true,"description":"Логотип бренда: оригинал + srcset + LQIP. null — логотипа нет.","type":"object","allOf":[{"$ref":"#/components/schemas/PublicImageEntity"}]},"logo_on_dark":{"type":"boolean","example":false,"description":"Логотип светлый — показывать на тёмной подложке (иначе сольётся)."},"products_count":{"type":"number","description":"Число товаров бренда в вертикали клиента.","example":128},"collections_count":{"type":"number","description":"Число коллекций бренда.","example":7},"in_stock_products_count":{"type":"number","description":"Число товаров бренда в наличии (публичный агрегат) — ось фильтра «Есть в наличии». 0 — в наличии ничего нет.","example":84},"suppliers":{"description":"Публичные поставщики бренда — выводятся из активных офферов его товаров (только поставщики с правом отдавать данные наружу). Ось фильтра «Поставщик» в директории.","type":"array","items":{"$ref":"#/components/schemas/PublicBrandSupplierEntity"}},"categories":{"description":"Категории бренда — выводятся из товаров (значения характеристики material), не хранятся на бренде. Значения совпадают с фильтром category в GET /brands.","type":"array","items":{"$ref":"#/components/schemas/PublicBrandCategoryEntity"}},"updated_at":{"type":"string","description":"Время последнего обновления (ISO 8601). Для инкрементального sync.","example":"2026-02-28T10:12:13.000Z"}},"required":["id","slug","name","country","country_code","country_slug","is_own_brand","description","logo","logo_on_dark","products_count","collections_count","in_stock_products_count","suppliers","categories","updated_at"]},"PaginatedBrandsEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicBrandEntity"}},"page":{"$ref":"#/components/schemas/PageMetaEntity"}},"required":["data","page"]},"BrandFacetBucket":{"type":"object","properties":{"value":{"type":"string","example":"ES","description":"Значение фильтра: alpha-2 страны или slug категории."},"name":{"type":"string","example":"Испания"},"count":{"type":"number","example":40,"description":"Число брендов."}},"required":["value","name","count"]},"PublicBrandFacetsEntity":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/BrandFacetBucket"}},"countries":{"type":"array","items":{"$ref":"#/components/schemas/BrandFacetBucket"}}},"required":["categories","countries"]},"CategoryResolveEntity":{"type":"object","properties":{"canonical":{"type":"string","example":"keramicheskiy-granit","description":"Текущий (канонический) slug."}},"required":["canonical"]},"PublicCollectionBrandEntity":{"type":"object","properties":{"slug":{"type":"string","example":"cifre"},"name":{"type":"string","description":"Отображаемое имя бренда (курируемое, если задано).","example":"Cifre"},"country_code":{"type":"string","nullable":true,"description":"Alpha-2 страны бренда — ключ флага и фильтра.","example":"ES"}},"required":["slug","name","country_code"]},"PublicCollectionEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0coll0001"},"slug":{"type":"string","example":"alchimia"},"name":{"type":"string","example":"Alchimia"},"brand_id":{"type":"string","nullable":true,"example":"clx0brand0001"},"brand":{"description":"Бренд коллекции (имя + страна) — карточке не нужен отдельный запрос в /brands.","allOf":[{"$ref":"#/components/schemas/PublicCollectionBrandEntity"}]},"description":{"type":"string","nullable":true,"description":"Описание коллекции (курация оператора).","example":"Коллекция под мрамор с матовой поверхностью."},"code":{"type":"string","nullable":true,"description":"Код коллекции у поставщика-владельца («Код коллекции 00713»).","example":"00713"},"interior_images":{"description":"Интерьерные изображения коллекции: оригинал + srcset + LQIP на каждое.","type":"array","items":{"$ref":"#/components/schemas/PublicImageEntity"}},"products_count":{"type":"number","description":"Число публичных товаров коллекции (ось сортировки popular).","example":24},"in_stock":{"type":"boolean","description":"Есть хотя бы один публичный товар коллекции в наличии.","example":true},"price_from":{"type":"number","nullable":true,"description":"Минимальная цена по публичным товарам коллекции («от X ₽»), руб.","example":1490},"price_max":{"type":"number","nullable":true,"description":"Максимальная цена по публичным товарам коллекции, руб. Это разброс по товарам коллекции (а не по офферам одного товара), поэтому отдаётся обеим поверхностям.","example":3200},"currency":{"type":"string","nullable":true,"description":"Валюта диапазона цен. null — цен нет.","example":"RUB"},"first_product_at":{"type":"string","nullable":true,"description":"Момент появления самого раннего товара коллекции (ISO 8601) — база бейджа «Новинка».","example":"2026-02-01T09:00:00.000Z"},"facets":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}},"description":"Объединение значений фильтруемых осей публичных товаров коллекции: ключ оси → slug'и её значений (чипы формата и свотчи цвета в карточке, таблица характеристик в шапке страницы коллекции). Кроме характеристик реестра есть встроенные оси витрины `format` (дериват размеров) и `placement` (назначение).","example":{"color":["belyj","seryj"],"surface":["matovaya"],"format":["60x120"],"placement":["floor"]}},"updated_at":{"type":"string","example":"2026-02-28T10:12:13.000Z"}},"required":["id","slug","name","brand_id","brand","description","code","interior_images","products_count","in_stock","price_from","price_max","currency","first_product_at","facets","updated_at"]},"PaginatedCollectionsEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCollectionEntity"}},"page":{"$ref":"#/components/schemas/PageMetaEntity"}},"required":["data","page"]},"PublicStoreEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0store0001"},"name":{"type":"string","example":"Склад на Ленина"},"address":{"type":"string","nullable":true,"example":"г. Москва, ул. Ленина, 1"},"latitude":{"type":"number","nullable":true,"description":"Широта точки на карте (±90). null — точка без геопривязки.","example":55.751244},"longitude":{"type":"number","nullable":true,"description":"Долгота точки на карте (±180). null — точка без геопривязки.","example":37.618423},"photo":{"nullable":true,"description":"Фото точки (склад/шоурум): оригинал + srcset + LQIP. null — нет фото.","type":"object","allOf":[{"$ref":"#/components/schemas/PublicImageEntity"}]},"is_active":{"type":"boolean","description":"Точка активна (наружу отдаём только активные — поле для явности контракта).","example":true},"updated_at":{"type":"string","example":"2026-02-28T10:12:13.000Z"}},"required":["id","name","address","latitude","longitude","photo","is_active","updated_at"]},"PaginatedStoresEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicStoreEntity"}},"page":{"$ref":"#/components/schemas/PageMetaEntity"}},"required":["data","page"]},"PublicStoreItemEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx0sitem0001"},"store_id":{"type":"string","example":"clx0store0001"},"product_id":{"type":"string","example":"clx0prod0001"},"price":{"type":"number","nullable":true,"description":"Отпускная цена на этом складе. null — строка несёт только наличие.","example":1490},"currency":{"type":"string","example":"RUB"},"in_stock":{"type":"boolean","nullable":true,"description":"Наличие на складе: qty>0 → true, qty=0 → false, null — остаток не указан.","example":true},"qty":{"type":"number","nullable":true,"description":"Остаток на складе, если у ключа есть scope stock:read (иначе null).","example":320},"updated_at":{"type":"string","example":"2026-02-28T10:12:13.000Z"}},"required":["id","store_id","product_id","price","currency","in_stock","qty","updated_at"]},"PaginatedStoreItemsEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicStoreItemEntity"}},"page":{"$ref":"#/components/schemas/PageMetaEntity"}},"required":["data","page"]},"MergedIntoProductEntity":{"type":"object","properties":{"public_id":{"type":"string","example":"clx0pub0002","description":"public_id живой карточки-цели (работает в /products/{id})."},"name":{"type":"string","example":"Alpha Tile Grey"},"slug":{"type":"string","example":"cifre-alpha-tile-grey-20x20"}},"required":["public_id","name","slug"]},"RemovedProductEntity":{"type":"object","properties":{"public_id":{"type":"string","example":"clx0pub0001","description":"public_id товара, каким он был виден клиенту."},"name":{"type":"string","example":"Alpha Tile"},"removed_at":{"type":"string","example":"2026-07-20T09:00:00.000Z"},"merged_into":{"nullable":true,"description":"Товар не пропал, а схлопнут в другую карточку (дедупликация): её public_id/slug. Прежний public_id тоже продолжает работать — /products/{id} отвечает редиректом на эту же цель. null — обычное снятие с публикации.","type":"object","allOf":[{"$ref":"#/components/schemas/MergedIntoProductEntity"}]}},"required":["public_id","name","removed_at","merged_into"]},"PublicChangesEntity":{"type":"object","properties":{"since":{"type":"string","example":"2026-07-01T00:00:00.000Z","description":"Эхо входного `since`."},"as_of":{"type":"string","example":"2026-07-31T10:00:00.000Z","description":"Момент, на который построена эта выдача. ОДИН на весь проход: на страницах 2, 3, … возвращается то же значение, что и на первой (окно зашито в курсор). Передайте его как `since` следующего прохода — тогда товар, обновившийся во время пагинации, придёт в следующем проходе, а не потеряется."},"new":{"description":"Товары, впервые появившиеся в срезе после `since` (created_at > since).","type":"array","items":{"$ref":"#/components/schemas/PublicProductEntity"}},"updated":{"description":"Уже известные товары с изменениями (цена/наличие/характеристики/т.д.) после `since`.","type":"array","items":{"$ref":"#/components/schemas/PublicProductEntity"}},"removed":{"description":"Товары, покинувшие публичный срез после `since` (сняты с публикации/потеряли качество/поставщик закрыл выдачу). Отдельный от `new`/`updated` список — исчезнувшую строку по `updated_at` не увидеть.","type":"array","items":{"$ref":"#/components/schemas/RemovedProductEntity"}},"page":{"description":"Курсор по объединённому потоку `new`+`updated` (сортировка -updated_at). `removed` в эту пагинацию не входит — см. `removed_truncated`. Двигайте `since` на `as_of` только после того, как выбрали проход до конца (`has_more: false`).","allOf":[{"$ref":"#/components/schemas/PageMetaEntity"}]},"removed_truncated":{"type":"boolean","example":false,"description":"true — удалений за окно больше, чем поместилось в `removed` (потолок 500). Догоняйте хвост через `removed_cursor`, не двигая `since`."},"removed_cursor":{"type":"string","nullable":true,"example":null,"description":"Курсор следующей страницы `removed` (или null — удалений за окно больше нет). Передайте его как `removed_cursor` следующего запроса с ТЕМ ЖЕ `since`, чтобы догнать массовое снятие с публикации без потери хвоста за потолком."}},"required":["since","as_of","new","updated","removed","page","removed_truncated","removed_cursor"]},"WebhookSubscriptionEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx9f…"},"url":{"type":"string","example":"https://dealer.example/hooks/best-api"},"events":{"type":"array","items":{"type":"string","enum":["price_changed","stock_changed"]}},"is_active":{"type":"boolean","description":"Доставка включена. Снимается автоматически, если приёмник не принимает подряд 30 попыток; вернуть — снова выставить в true (счётчик неудач обнулится)."},"description":{"type":"string","nullable":true},"created_at":{"type":"string","example":"2026-08-12T10:00:00.000Z"},"last_success_at":{"type":"string","nullable":true,"description":"Последняя успешная доставка."},"last_failure_at":{"type":"string","nullable":true},"last_error":{"type":"string","nullable":true,"description":"Почему не доехало в последний раз — без похода в поддержку."},"consecutive_failures":{"type":"number","description":"Неудачных попыток подряд. Успешная доставка обнуляет; на 30 подписка отключается автоматически (`is_active: false`), причина — в `last_error`."},"previous_secret_expires_at":{"type":"string","nullable":true,"description":"До этого момента ЕЩЁ работает секрет предыдущей ротации (окно совместимости — вторая подпись в `X-Webhook-Signature`, через запятую). null — ротации не было либо срок истёк."}},"required":["id","url","events","is_active","created_at","consecutive_failures"]},"CreateWebhookDto":{"type":"object","properties":{"url":{"type":"string","description":"Адрес приёмника (только https, публичный хост). POST с телом события и подписью.","example":"https://dealer.example/hooks/best-api"},"events":{"type":"array","description":"События, которые толкать на этот адрес.","example":["price_changed","stock_changed"],"items":{"type":"string","enum":["price_changed","stock_changed"]}},"description":{"type":"string","description":"Метка для себя («прод-приёмник», «стейдж»).","maxLength":200}},"required":["url","events"]},"CreatedWebhookSubscriptionEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx9f…"},"url":{"type":"string","example":"https://dealer.example/hooks/best-api"},"events":{"type":"array","items":{"type":"string","enum":["price_changed","stock_changed"]}},"is_active":{"type":"boolean","description":"Доставка включена. Снимается автоматически, если приёмник не принимает подряд 30 попыток; вернуть — снова выставить в true (счётчик неудач обнулится)."},"description":{"type":"string","nullable":true},"created_at":{"type":"string","example":"2026-08-12T10:00:00.000Z"},"last_success_at":{"type":"string","nullable":true,"description":"Последняя успешная доставка."},"last_failure_at":{"type":"string","nullable":true},"last_error":{"type":"string","nullable":true,"description":"Почему не доехало в последний раз — без похода в поддержку."},"consecutive_failures":{"type":"number","description":"Неудачных попыток подряд. Успешная доставка обнуляет; на 30 подписка отключается автоматически (`is_active: false`), причина — в `last_error`."},"previous_secret_expires_at":{"type":"string","nullable":true,"description":"До этого момента ЕЩЁ работает секрет предыдущей ротации (окно совместимости — вторая подпись в `X-Webhook-Signature`, через запятую). null — ротации не было либо срок истёк."},"secret":{"type":"string","description":"Секрет подписи доставок (HMAC-SHA256). Показывается ОДИН раз — сохраните его.","example":"whsec_9pQ…"}},"required":["id","url","events","is_active","created_at","consecutive_failures","secret"]},"UpdateWebhookDto":{"type":"object","properties":{"url":{"type":"string","example":"https://dealer.example/hooks/best-api"},"events":{"type":"array","items":{"type":"string","enum":["price_changed","stock_changed"]}},"is_active":{"type":"boolean","description":"Пауза/возобновление доставки без потери секрета и истории."},"description":{"type":"string","maxLength":200}}},"RotatedWebhookSecretEntity":{"type":"object","properties":{"id":{"type":"string","example":"clx9f…"},"url":{"type":"string","example":"https://dealer.example/hooks/best-api"},"events":{"type":"array","items":{"type":"string","enum":["price_changed","stock_changed"]}},"is_active":{"type":"boolean","description":"Доставка включена. Снимается автоматически, если приёмник не принимает подряд 30 попыток; вернуть — снова выставить в true (счётчик неудач обнулится)."},"description":{"type":"string","nullable":true},"created_at":{"type":"string","example":"2026-08-12T10:00:00.000Z"},"last_success_at":{"type":"string","nullable":true,"description":"Последняя успешная доставка."},"last_failure_at":{"type":"string","nullable":true},"last_error":{"type":"string","nullable":true,"description":"Почему не доехало в последний раз — без похода в поддержку."},"consecutive_failures":{"type":"number","description":"Неудачных попыток подряд. Успешная доставка обнуляет; на 30 подписка отключается автоматически (`is_active: false`), причина — в `last_error`."},"previous_secret_expires_at":{"type":"string","nullable":true,"description":"До этого момента ЕЩЁ работает секрет предыдущей ротации (окно совместимости — вторая подпись в `X-Webhook-Signature`, через запятую). null — ротации не было либо срок истёк."},"secret":{"type":"string","description":"Новый секрет подписи — переносите его на приёмник. Старый ещё действует до `previous_secret_expires_at` (окно совместимости), затем перестаёт приниматься.","example":"whsec_9pQ…"}},"required":["id","url","events","is_active","created_at","consecutive_failures","secret"]},"WebhookPingEntity":{"type":"object","properties":{"ok":{"type":"boolean","description":"Приёмник ответил 2xx."},"response_status":{"type":"number","nullable":true,"description":"Код ответа приёмника. null — не дозвонились вовсе.","example":200},"error":{"type":"string","nullable":true,"description":"Почему не доехало (таймаут, DNS, код ответа)."},"sent_at":{"type":"string","description":"Когда отправили пинг."},"delivery_id":{"type":"string","description":"Id строки в ленте доставок (`GET /webhooks/{id}/deliveries`) — там же виден этот пинг."}},"required":["ok","sent_at","delivery_id"]},"WebhookDeliveryEntity":{"type":"object","properties":{"id":{"type":"string"},"event":{"type":"string","enum":["price_changed","stock_changed","ping"]},"status":{"type":"string","enum":["PENDING","DELIVERED","FAILED"]},"attempts":{"type":"number","description":"Сделано попыток."},"payload":{"type":"object","additionalProperties":true,"description":"Тело события, как оно ушло (поле data конверта)."},"response_status":{"type":"number","nullable":true,"description":"Код ответа приёмника."},"last_error":{"type":"string","nullable":true},"created_at":{"type":"string"},"delivered_at":{"type":"string","nullable":true},"next_attempt_at":{"type":"string","description":"Когда будет следующая попытка (для PENDING)."}},"required":["id","event","status","attempts","payload","created_at","next_attempt_at"]},"PublicMenuPresetEntity":{"type":"object","properties":{"slug":{"type":"string","example":"pod-derevo","description":"Сегмент URL после раздела: /catalog/keramogranit/pod-derevo."},"name":{"type":"string","example":"Керамогранит под дерево"},"product_count":{"type":"number","example":420,"description":"Публичных товаров под пресетом (раздел + залоченный фильтр). Пустые пресеты в меню не попадают."}},"required":["slug","name","product_count"]},"PublicMenuCategoryEntity":{"type":"object","properties":{"slug":{"type":"string","example":"keramogranit","description":"Значение фильтра category и сегмент URL раздела."},"name":{"type":"string","example":"Керамогранит"},"product_count":{"type":"number","example":4210,"description":"Товаров в разделе ВМЕСТЕ с подразделами."},"presets":{"description":"Курируемые подкатегории-лендинги раздела в порядке показа (курация оператора). Раздел без курации — пустой список.","type":"array","items":{"$ref":"#/components/schemas/PublicMenuPresetEntity"}},"children":{"description":"Подразделы в порядке вывода.","type":"array","items":{"$ref":"#/components/schemas/PublicMenuCategoryEntity"}}},"required":["slug","name","product_count","presets","children"]},"PublicMenuEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenuCategoryEntity"}}},"required":["data"]},"PublicCategoryEntity":{"type":"object","properties":{"slug":{"type":"string","example":"keramogranit","description":"Значение фильтра category и сегмент URL раздела."},"name":{"type":"string","example":"Керамогранит"},"product_count":{"type":"number","example":4210,"description":"Товаров в разделе ВМЕСТЕ с подразделами (фильтр category работает так же)."},"children":{"description":"Подразделы в порядке вывода.","type":"array","items":{"$ref":"#/components/schemas/PublicCategoryEntity"}}},"required":["slug","name","product_count","children"]},"PublicCategoriesEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCategoryEntity"}}},"required":["data"]},"PublicCategoryFacetGroupAxisEntity":{"type":"object","properties":{"key":{"type":"string","example":"placement","description":"Ось /products/facets: ключ характеристики (фильтр — attr=<key>:<slug>) либо встроенная ось витрины (brands → brand, formats → format, …)."},"name":{"type":"string","example":"Назначение","description":"Дефолтное имя САМОЙ оси (не подпись группы) — чем чипы этой оси отличаются от соседних внутри той же группы."}},"required":["key","name"]},"PublicCategoryFacetGroupEntity":{"type":"object","properties":{"key":{"type":"string","example":"color","description":"Ключ оси: ключ характеристики (ось attributes у /products/facets и префикс пары attr у /products) либо встроенная ось витрины (brands → brand, formats → format, size_categories → size_category, price, in_stock, brand, collection, category, kind, placement)."},"name":{"type":"string","example":"Цвет","description":"Готовая подпись группы (курируемая или имя характеристики)."},"type":{"type":"string","enum":["list","range","bool"],"example":"list","description":"Тип оси = контрол фильтра и способ её передать: list — список значений с множественным выбором (значения и счётчики в /products/facets, фильтр — повторяемый attr=<key>:<slug> у характеристики либо brand/collection/category/kind/placement/format/size_category у встроенной оси); range — числовой диапазон (границы в /products/facets, фильтр — пара *_min/*_max, сейчас это price); bool — да/нет (встроенная ось in_stock — булев параметр, характеристика — два значения через attr). Задаётся реестром оси, курацией не меняется."},"axes":{"description":"Оси группы в порядке показа, первая — та, чей ключ у группы. Обычная группа — массив из одной оси, композитная («Назначение» = placement + application + workType, «Размер» = size_category + format) — из нескольких: чипы всех осей рисуются одним блоком, но каждый фильтрует СВОИМ параметром. Тип у всех осей группы общий (type выше).","type":"array","items":{"$ref":"#/components/schemas/PublicCategoryFacetGroupAxisEntity"}}},"required":["key","name","type","axes"]},"PublicCategoryFacetGroupsEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCategoryFacetGroupEntity"}},"curated":{"type":"boolean","example":true,"description":"Явный флаг наследования по дереву разделов: `true` — раздел или его предок курирован (состав задан явно, даже если оператор выключил в нём все оси); `false` — состав не задан НИГДЕ по ветке. Пустой `data` при `true` значит «показывать нечего» (оператор снял все оси намеренно), пустой `data` при `false` — «курации нет вовсе», клиент вправе подставить свой дефолтный набор осей."}},"required":["data","curated"]},"PublicCategoryPresetEntity":{"type":"object","properties":{"slug":{"type":"string","example":"pod-derevo","description":"Сегмент URL пресета после раздела: /catalog/keramogranit/pod-derevo."},"name":{"type":"string","example":"Керамогранит под дерево"},"title":{"type":"string","example":"Керамогранит под дерево — купить","nullable":true,"description":"SEO-title пресета; null → страница генерирует по имени и счётчику."},"description":{"type":"string","example":"Керамогранит с эффектом дерева от поставщиков…","nullable":true,"description":"SEO-description пресета; null → генерируется страницей."},"attr":{"example":["effect:pod-derevo"],"description":"Залоченный фильтр по характеристикам (пары «ключ:slug», формат attr /products): применяется к выдаче пресета и скрывается из панели фильтров.","type":"array","items":{"type":"string"}},"builtin":{"example":["format:60x120"],"description":"Вторая половина залоченного фильтра — ВСТРОЕННЫЕ оси витрины парами «ось:значение» (format, size_category — параметры /products): лендинги «60×120» и «больших размеров» держатся на них. Применяются и скрываются из панели так же, как attr.","type":"array","items":{"type":"string"}},"product_count":{"type":"number","example":420,"description":"Публичных товаров под пресетом (раздел + залоченный фильтр). Пустые пресеты наружу не отдаются (thin content)."}},"required":["slug","name","title","description","attr","builtin","product_count"]},"PublicCategoryPresetsEntity":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCategoryPresetEntity"}}},"required":["data"]},"DemoCatalogTotalsEntity":{"type":"object","properties":{"products":{"type":"number","example":48213,"description":"Публичных товаров в каталоге."},"brands":{"type":"number","example":214,"description":"Брендов с публичными товарами."},"collections":{"type":"number","example":2342,"description":"Коллекций с публичными товарами."},"categories":{"type":"number","example":37,"description":"Разделов каталога (дерево /categories)."},"suppliers":{"type":"number","example":13,"description":"Поставщиков, чьи данные отдаются наружу."},"updated_at":{"type":"string","nullable":true,"example":"2026-07-28T09:12:00.000Z","description":"Когда каталог менялся в последний раз."}},"required":["products","brands","collections","categories","suppliers","updated_at"]},"DemoBrandEntity":{"type":"object","properties":{"slug":{"type":"string","example":"cifre"},"name":{"type":"string","example":"Cifre"},"products_count":{"type":"number","example":128}},"required":["slug","name","products_count"]},"DemoSliceEntity":{"type":"object","properties":{"brand":{"nullable":true,"description":"Бренд демо-среза. null — ключ не демо-личность (обычный клиент видит весь каталог).","type":"object","allOf":[{"$ref":"#/components/schemas/DemoBrandEntity"}]},"products":{"type":"number","example":96,"description":"Товаров в демо-срезе — столько песочница отдаёт по /products."}},"required":["brand","products"]},"DemoCollectionEntity":{"type":"object","properties":{"slug":{"type":"string","example":"alchimia"},"name":{"type":"string","example":"Alchimia"},"brand_slug":{"type":"string","nullable":true,"example":"cifre"},"products_count":{"type":"number","example":24}},"required":["slug","name","brand_slug","products_count"]},"PublicDemoStatsEntity":{"type":"object","properties":{"totals":{"$ref":"#/components/schemas/DemoCatalogTotalsEntity"},"slice":{"$ref":"#/components/schemas/DemoSliceEntity"},"brands":{"description":"Крупнейшие бренды каталога со счётчиками (имена, без товаров).","type":"array","items":{"$ref":"#/components/schemas/DemoBrandEntity"}},"collections":{"description":"Крупнейшие коллекции каталога со счётчиками (имена, без товаров).","type":"array","items":{"$ref":"#/components/schemas/DemoCollectionEntity"}}},"required":["totals","slice","brands","collections"]}}}}