Catalogue: products, categories, filters, images
/docs/cataloguecurrentМашинный перевод с английского· проверено 2026-08-19
Статус: current · проверенный: 2026-08-19/20 (§3–§6, §9) + 2026-08-30 (§7 galleries, live curl против подставки «Волна-3») владелец: гладиатр API документация · Служить: GET /docs/catalogue · мастерство: vizen-catalog Связанный путеводитель: /docs/catalog-import — тот import *сценарий*, другой document
Обновление 2026-10-21: меняя type из существующих section; document Ворота - это TestAreaCopiesInSync и TestAreaHasEssentials, поведением, которое охватывается Интеграция Suite category_type_change_integration_test.go.
1.Что вы можете сделать здесь
Прочти shop's каталог — categories, products, цены, attributes, images — и Заполните: создайте sections и products, прикрепить фотографии, провод variants связанный products, publish. The shop Избранный по key, не по параметру.
2. Решать first
| Если ты хочешь… | Возьми это path | Стоимость |
|---|---|---|
| а product row на page | productListing widget — widgets.md | live data, look ограниченный props |
| а product row в твоем own design | own разметка над live feed products[] — own-markup.md §2 | live цены; выбрано attributes и group Цены замораживаются только в случае ввода в разметку |
| один card, Множество комбинаций | variants внутри одного product | один URL, не разделять SEO |
| а URL и content в комбинации | связанный products в group | Н. products плюс group поддерживать |
Когда variant должен быть look Выберите один из трех — они не заменят друг друга:
| Ты хочешь | Механизм | Где это написано | Стоимость |
|---|---|---|---|
| другой чип и телега-row photo, Один общий слайдер | тот set's image_id | PUT /products/{id}/variants | Один снимок на каждый set |
| Различные фотографии в слайдере, один product, один URL | Галерея плюс set's gallery_id | POST /galleries → PUT /products/{id} → PUT /products/{id}/variants | ≤15 galleries на объект × ≤24 items; SEO Остаться разделенным |
| а URL, SEO, text и карточка его сетки own | связанный products | Н. × POST /products + /product-groups | Н. products для обслуживания; дополнительные карты hidden с catalog_hidden |
image_id и gallery_id являются независимыми fields: тот first Двигает чип и cart row, Второй - только слайдер. variants-vs-связанный-products выбор сделан один раз и стоит дорого; full Сравнение находится в GET /docs/catalog-import §4.
2.1 Изменить type из существующих section
Используйте существующий PUT /categories/{id} с {"item":{"type":"page"}}. Четыре admin choices есть product, page, news и site; наследие model Остается поддерживаться API. А. PAT требует существующих catalog:write scope; тот shop и разрешены contour исходит из аутентифицированного контекста, а не из item
- параметр компании. new scope или конечная точка.
The admin Называется эта область Структура сайта (Структура сайта; URL остаются неизменными.
Настоящий type Перемены требуют нежилого section без live прямой ребенок sections. CATEGORY_HAS_CHILDREN средство move или удалить этих детей first. CATEGORY_HAS_CONTENT означает target cannot принять непосредственное products или статьи: product Членство включает первичное и дополнительное categories, и Неопубликованные статьи count. Сохраненный site-папка document с source/assets или Спасенный HTML , в том числе неактивный, дает CATEGORY_HAS_SITE_CONTENT. Ан empty обслуживание document Только один не block. Такая же защита также препятствует восстановлению спящего состояния. site's Старое содержание Обратно вспять site. HOME_CATEGORY_TYPE_FIXED Защищает дом page. Наследие model target Сохраняет свое существующее MODEL_MUST_BE_LEAF ребенок error. Неизвестные типы дают INVALID_CATEGORY_TYPE; иностранный section дарить NotFound не раскрывая его содержания. Отправить свойства в item; перемещать section По-прежнему использует /categories/move, не item.parent_id об этом PUT.
Эти отказы оставляют редакцию без изменений. API не конвертировать, move, отсоединить или удалять content. Сохраняйте ID, slug, SEO и design; Подготовьте содержимое, используя Существующие API before Перезапись, отправка current type, или опустить type, Позволяет редактировать обычные свойства. DEV Редактировать contour-Местные. Изменения типа и записи, которые прикрепляют или восстанавливают products, статьи, дети или site content Обмен транзакционным category-tree Заблокировать и перепроверить эффективный category before Включает в себя партию product импорт и доверие DEV site-Затхлый лист. content писать может быть отвергнутым after Другой запрос меняет target type; Выберите совместимый section before Опубликовать валидацию applied type изменения и applied new/moved/restored content ссылки на конечное состояние, исключая keep_prod rows, и откатывает несовместимую публикацию.
The category PUT, последующий move, и card settings Отдельные операции. А. failed Последующая операция не отменяет подтвержденного PUT. Используйте возвращенный category показать сохраненные свойства, а затем повторно использовать только неподтвержденную операцию;.
3.Фильтры нуждаются в filter. prefix — Это не косметический
Измеряемый на live API, shop kiberpank (Компания 11, вызывает одного отдельно:
GET /products?filter.company_id=11&page.number=1&page.limit=1 → total 426
GET /products?filter.company_id=11&…&category_id=181 → total 426 ← swallowed
GET /products?filter.company_id=11&…&filter.category_id=181 → total 60 ← works
GET /products?filter.company_id=11&…&filter.slug=abc → total 426 ← swallowedВсе четверо ответили 200. параметр без prefix — и любой unknown filter.* Также — выпало без слова: грпк-gateway's default query парсер Игнорирует пути, которые cannot Карта и единственная top-level fields По просьбе является filter, page, sort, attribute_filters, attribute_codes и group_mode (Последние два — §4.1).
Чек, который зацепил все это class: compare total before и after Добавить filter. Неизмененный означает, что параметр не был понят. extra звонить new filter, Это единственный сигнал, который вы получаете..
Анонимные звонящие получают один перерыв: без filter.company_id арендатор cannot быть resolved И звонит громкий (403 AUTH_COMPANY_PROBLEM). С помощью токена Арендатор приходит из key, Ничего не ломается — вы просто получаете все.
4. существующие фильтры
| Параметр | значение |
|---|---|
filter.category_id | products одного section |
filter.include_subtree | с category_id: это section и каждый section под ним гнездится |
filter.ids | несколько products сразу — Повторить параметр |
filter.query | Поиск по name |
filter.is_published | опубликовано только |
filter.min_price / filter.max_price | price range |
filter.company_id | который shop (Анонимные чтения; с токеном key решать) |
page.number / page.limit | paging, number Начинается с 1, limit 1–100 |
sort.field / sort.order | заказывать: name, price, created_at, category_id; ids — тот order из filter.ids (§4.1) |
attribute_filters[0].attribute_id, .values, .min, .max | Фирмы, индексированный синтаксис, max 20 |
attribute_codes | selected attributes на всех item (§4.1) — Повторить параметр, order вопросы, max 32 |
group_mode=collapse | один position за group связанный products (§4.1) |
filter.ids является повторным field, Не список: filter.ids=1399,1400 ответы 400 `INVALID_REQUEST`, filter.ids=1399&filter.ids=1398 Ответы 200 с total: 2. Измеряется двумя способами.
4.1 Выбранный attributes и одной плиткой на product group
Два аддитивных параметра одного и того же GET /products. Без них ничего extra Прочитано; прочитано new fields И все же вернуться к своим empty values (group_id: "0", нет attributes, groups, group_members), Клиент, который игнорирует unknown fields Посмотреть Старый ответ.
покупатель visibility является необходимым условием для selected attributes и group топоры. Настройте его /docs/catalogue §8.3 before Диагностика a plain Плитка как а group жучок.
`attribute_codes` — коды атрибутов для return на всех item, как ProductShort.attributes[] в тот order Ты спросила: …&attribute_codes=bed-size&attribute_codes=material. Каждый элемент является {attribute_id, code, name, data_type, section, display, value, options[]}; display - готовая строка (единицы, ярлыки опций)), value является сырым value (google.protobuf.Value: Струна, а number, Булева или список), options[] являются выбранными options из одного select/multiselect (id, value, label, color_hex, image_url). The int64 ids — attribute_id, options[].id, и в groups[] group_id и axes[].attribute_id — путешествие как струны. Только attributes Покупатель может видеть в списках Возвращаясь, а hidden code и ан unknown Один из них в равной степени отсутствует. code rule это ^[a-z][a-z0-9_-]{0,63}$; а code что нарушает его, рассматривается как unknown, не как error. Более 32 кодов (считано) before дубликаты выпадают или code длиннее 64 символов, является 400 с валидатором's message в body. attribute_codes=a,b это ONE code a,b — Он разрушает rule и молча отсутствует; повторяем параметр.
`group_mode=collapse` — один position за product group (связанный products с такими осями, как size и цвет, /product-groups). А. group находится в result когда любой член проходит все фильтры; position Это лучший член для запрошенный sort (через default — тот first в group's член order; а каталог-hidden член never это). total и paging count groups, так page граница never - Разлучает семью. filter.parent_product_id Это а 400 GROUP_MODE_PARENT_UNSUPPORTED.
Рядом с result Ответ затем несет:
| Поле | Что это такое |
|---|---|
result.items[].group_id | тот position's group; int64, как веревка: "0" — нет group, или переключение недоступно |
groups[] | только groups это переключатель (по меньшей мере два members Рассказываемые по видимым осям), в order из `result.items`: axes[] (attribute_id, code, name, display, options[] — Весь словарь: id, value, label, color_hex, image_url, sort_order) и все опубликованный members Покупатель может видеть — values по оси code, url, slug, preview_url, image_url. is_current Отметить члена, который стоит как position |
group_members[] | full cards (ProductShort: own price, запас, картинка, url) из members Это не позиции, которые оцениваются одинаково. code как items |
Рецепт — постройте переключатель из одного ответа:
- Для position
pнайтиgroups[]сgroup_id == p.group_id(compare как струны). - Член's full card является элементом
result.itemsилиgroup_membersчейidравный члену'sproduct_id. Нет. card — Откройте член своейurlвместо. - Чип оси
aдля valuev: Член, которыйvaluesравный current член's на каждой оси иvнаa. Нет — комбинация не Есть: Показать инвалид, never заменить другой SKU. Члены редкого group может быть недостижимым с помощью одноосевых чипов — предложить «все» variants" список. - Члены делают не Выполняйте просьбу's фильтры (другие) section, каталог-hidden members Законны в семье) — never Выводим их в отдельные позиции.
пределы: full cards — максимум 16 на человека group 200 на человека page Вместе с тем, Позиции; матрица в groups[] это never Не полагайтесь на эти цифры. — проверить, является ли card Члены перепроверяются (опубликованы, доступны)) когда cards Прочитано; а group Оставшийся один член является plain position.
Список избранных в вашем order — `filter.ids` + `sort.field=ids`. The items приходить в order из списка, а не по дате. group_mode=collapse перечисленный product это тот position его group: Список один член на одного члена group Выберите оба варианта order Для плитки и для variant показанный first; Остальная часть каждого group все еще приходит в groups[] / group_members. Если два selected ids принадлежат к одному и тому же group, Они производят одна плитка: тот first имеющий право на получение id В списке выбирается его position и начальный SKU. Это также относится к вставке productsIds (/docs/own-markup §2). Перечисленный id который не имеет права (draft, каталог-hidden, закрытый доступ) не дает position и не выбрано заменяющего члена: если нет другие перечисленные id Семья, в которой он находится, не имеет права на это. feed. sort.field=ids без filter.ids ничего не делает.
GET /products?filter.company_id=19&filter.ids=2503&filter.ids=2509&filter.ids=2498
&sort.field=ids&group_mode=collapse&attribute_codes=bed_size&attribute_codes=bed_color
&page.number=1&page.limit=48
→ items 2503, 2509, 2498 in that order — each the shown member of its groupОба параметра отвечают как покупатель для каждого абонента.. The result предназначен для a shop page Каждый посетитель видит, что shop's own key, тот owner's сеанс и тот dev preview Получить то, что получает анонимный покупатель: опубликовано, а не каталог-hidden products, цены покупателя, доступ покупателя, видимые покупателями аспекты и attributes, нет внутренний price columns. Для full owner view использовать plain запросить или GET /products/{id}. Ось hidden от покупателей в списках удаляется с его values и link pictures; если members Неразличимым становится, group Возвращается как а plain position (group_id "0").
Зачатие, измеренное: page.limit=101 → 400 (value must be inside range [1, 100]); page.limit=100 Только 200; page.number=2 400, только о том, что пропавший Limit. Отправьте обе половины, и асимметрия перестанет иметь значение.. ⚠️ /docs/catalog-import §2.2 Указывается обратное rule; Измерение выше является тот current поведение.
**filter.category_id Одиночество означает section *буквально*.** А. product принадлежать в section через category_id или categories[] — Ничто не ходит по tree. В а shop товары которых висят на листе sections (марок, моделей и чьих top section Это всего лишь папка, которая top section ответы total: 0 Пока дети держат Весь каталог. filter.include_subtree=true агрегированные по section и каждый section Измеряется по папке section 1128 - два бренда sections, Один из которых имеет гнездо line его own:
GET /products?filter.company_id=12&filter.category_id=1128 → total 0
GET /products?filter.company_id=12&filter.category_id=1128&filter.include_subtree=true → total 4
GET /products?filter.company_id=12&filter.category_id=1129 → total 2 ← one brand
GET /products?filter.company_id=12&filter.category_id=1129&filter.include_subtree=true → total 3 ← + its nested lineThe default неизменен и остается буквальным чтением, поэтому admin Список " products из этого section" Продолжает считать, что на самом деле там прикреплено. Работает только вместе с category_id — на его own Это один из молчаливых Удаленные параметры §10. sections не являются частью дерева, и ходить never оставляет shop кто владеет корнем section. page.*, sort.* и attribute_filters применяется к агрегированным set Как это делается для одного section; filterable_attributes В этом же ответе описывается section Вы просили (его) own фильтруемый definitions), Не союз своих детей's. Тот же параметр name и то же значение, что и GET /articles?include_subtree=true для news Рубрики — единственное отличие filter. prefix, который /products Требуется для каждого filter (§3).
5.Формы, которые удивляют
- `result` Это не одно и то же type повсюду.
/productsвозвращает объект{items, total, currentPage};/categoriesвозвращает plain массив — 84 categories Для компании 11, нетtotal, нет paging; - тот shop Параметр отличается от конечной точки.
/productsхотетьfilter.company_id;/categoriesхотеть top-levelcompany_idи ответы403 COMPANY_ID_PROBLEMдля префиксированных form. Измеряют оба; - `category.id` это number и `category.parent_id` Это веревка (
107vs"107"), как и естьroot_id. The rule Обобщения: Proto3 JSON рендерингint64как струна иint32как number, так type из одного id последующий field, не Конечная точка. ids есть UUID струны; - а category Также является page.
typeэтоproduct,pageилиnews— приземление pages live В том же списке, что и sections. ⚠️ Но `type: "page"` category не render ограниченный `body` slot еще. Привязка принята, читается обратно какsource: "own"И не берёт ничего, а кладовая поддерживает это. type это Пока не будет, посадка - этоtype: "product"category без родительский — тот же address, то же самое slots, и section Просто не имеет products; - `parent_id` Меняется только путем `POST /categories/move`. Отправить *различный* родительский
PUT /categories/{id}Отказано вCATEGORY_PARENT_USE_MOVE(Раньше его молча бросали,200и старое Перемещение узла должно проверить, tree для циклов, глубина и хозяев, и это живет вmoveОтправляя родителя, он category уже принят и ничего не делает; - а move Изменения адресов — из category, их подкатегории и их products. Звони.
POST /categories/moveсdry_run: truefirst, шоу пользовательurl_changesИ спросить, не pin Старые адреса с правилами (pin_rules: true). URL жизненный цикл (move, переименовать, удалить):/docs/redirects; product.priceэто{price, old_price, currency, promotion_name}; Последние два Как правило, empty (§9);- `preview` может быть заимствован.
preview_auto: trueозначает product не имеет preview его own Сервер показал, что first image item его first Вместо этого, не пишите, что id обратно вpreview_id— Ты получишь дублировать; gallery_idsиgallery_idесть Массивы струн / A string, не Цифры: они естьint64fields, и прото3 JSON рендерингint64как веревка."0"в variant'sgallery_idозначает "нет" link".
Спросить category Список для меньшего fields
GET /categories возврат каждый category из shop с каждый field — описания, в целом SEO block, авторы, временные метки, счетчики. shop с 704 sections ответы 679 375 bytes, И витрина просит этот список каждый page render, Потому что это единственный public source из parent_id (меню, breadcrumbs, tree URL. Почти none Из него читают.
GET /categories?company_id=182&view=menuview= | Что возвращается |
|---|---|
отсутствующий или full | Полное Category — Для байта, что всегда было, Ничего из того, что вы уже написали, не меняет |
menu | Поля, которые нужны для навигации |
tree | Ответ идет на `nodes` (не result): а light узел на category — id, parent_id (отсутствует в корне categories: Прото3 JSON опущение 0), name, order, type, slug, is_published, is_show_menu, is_home, is_deleted, count_products (owner только), count_articles, dev_dirty, preview_url. Для рисования category tree или пикер: около 8 KB Сжатый для 650 sections вместо того, чтобы ~75 KB. Открыть category's full данные с GET /categories/{id} |
deleted_only=true (owner из shop Только возвращение только deleted categories, без live Исполнитель: "Show" deleted" bin; на витрине магазина параметр ignored. Исключено categories never Перемешаны в обычный список.
⚠️ `is_show_menu` Решает ли А category появляется в навигации. The menu, тот header's fallback Ссылки и The footer's Автоматический каталог Все колонки пропускают category с is_show_menu:false — и пропустить весь ветвь, так что hidden родитель never Дети его в сироту, top level. Дефолт есть true: опустить field в POST /categories и category в тот menu. Чтобы построить посадку page Не прикасаясь к shop's Навигация, посылать {"name":"Sale","type":"page","is_show_menu":false} — тот page Достижимо посредством его own URL и любой link Ты его надела.. PUT /categories/{id} Почетный value Вы отправляете и оставляете его в покое, когда вы его опускаете..
menu заполняет именно эти: id, name, seo.slug, parent_id, order, type, is_published, is_show_menu, is_home, is_deleted, preview.url, count_articles, sidebar_config. Все else Возвращаться empty или нулевой: description, остальной seo (page_title, meta_title, meta_description, meta_keywords, og_image_id, og_image_url, noindex, nofollow), preview_id, preview.id, count_products, root_id, page_template, created_at / updated_at, created_by / updated_by, comments_enabled, questions_enabled, gallery_ids, galleries.
Тот, который category чей description и SEO Ты действительно render Это тот, который Посетитель открылся — попросите его сам: GET /categories/by-slug/{slug} (или /categories/{id}) Ответы с помощью full Форма, и ты все равно ее приносил. для page's blocks и design.
Измеряется на том же 704-section shop: 679 375 → 542 801 bytes, и SQL За ним стоит 13 columns Вместо 30. Оставшийся вес реален. content — section имена и preview URL-адреса — плюс JSON скелет: это API Каждый из них field из одного message, В том числе и empty Так что ключи Они остаются в ответе.
Ан unknown value ответы 400 CATEGORIES_VIEW_UNKNOWN Вместо того, чтобы молчать отступать назад к full, Для того же reason Галерея проекция делает: "I «Ничто не стало легче» — это единственный провал, который вы не могли отладка.
5.1. combo_items[] — Содержание a set
А. set Цена определяется его составом. Над kind:"combo" product сервер вычисляет как цифры, так и игнорирует product's own хранить price fields: price Это сумма в-set цены, old_price Сумма компонентов'
- Цены на витрины. Эти же два числа приходят из card, от каждого
listing и от него cart. old_price это empty Когда нет ничего дешевле внутри set — а card never ударить его own price. Писать price в set все еще принимается и хранится; сохраненная фигура просто never Достигнуть покупателя, и возвращается в употребление, если состав опорожнен (второй вариант). product поворачивается назад вводить kind:"standard").
GET /products/{id} на kind:"combo" product несет в себе компоненты:
| Поле | Что это такое |
|---|---|
product_id, name, slug | компонент product |
url | Витрина магазина address, Тот же канон, что и Product.url Пусто, когда нет канонического path (нет slug, или slugless/dead предок) |
preview | MiniFile компоненту |
price, old_price | компонент's own выставка price |
quantity | Сколько их было set держать |
price_mode, price_value, effective_price, base_price | Цены внутри него set |
⚠️ Названия полей в вашем own Маркировка не является этими именами. The page scope использование c.effective_price / c.price_text Для компонента в product.combo_items[]; c.item_price принадлежит оферте в product.in_combos[], не для того, set’s Композиция: оба выставляют c.url. The scope Создан для написания разметки, Не для зеркального REST; Замены не имеют выражений.Читать далее /docs/own-markup для scope имена; Не думайте, что один список переводится на другой..
6. Изображения и резистор
А. file address возвращается в одной из двух форм, в зависимости от того, как shop магазины его files: ан absolute хранение URL (https://storage.yandexcloud.net/vizen-prod-files/sunset/view/product/…png) или path на shop само собой (/files/sunset/view/product/…png). Либо Как это было оригинально, never Что вы наденете на page.
Не пиши рецепт, который принимает одну форму. address тот API давать Вы, разберите схему и хост, если он у вас есть, и поставьте резизер. prefix в Перед тем, что осталось:
url = item.file.url # or preview.url, gallery[].url
path = url.replace(/^https?:\/\/[^/]+/, '') # absolute → path; a path stays a path
src = `/w/${width}${webp}${path}` # same-origin on the shop domainThe /webp сегмента является не always Там: добавляется для растровых images это не являются веб-сайтами и опускаются, когда source это .webp или SVG. Так /w/640/webp/files/…jpg и /w/640/files/….webp Оба они делают рециркуляции одно и то же — два разных источника, один механизм. тот source расширение, а не жесткое кодирование, или просто принять оба, когда Проверяйте свою работу.
⚠️ Галерея item Сохраняет свои address в `items[].file.url`, не items[].url.
Предварительно сгенерированные ширины: 32 · 64 · 128 · 320 · 640 · 1024 · 1600 · 2048 · 2560. Другие работают, но вычисляются на лету и на лету. first Посетитель ждет. шага, по крайней мере, дважды CSS size. Качество имеет шаг default, переопределяемый с ?q=40..95; СВГ не изменяют размер. Измеряется по одному product photo: оригинал 1 555 258 bytes против 9 836 для /w/320/webp/… — 158x для 320px card.
Для image URL проверки с HEAD / GET, использовать /docs/troubleshooting §3 (старый HEAD-404 это только fixed).
7.Галереи - это resource их own
Галерея это не field на product. Это компания-level resource с его own CRUD; products, categories (каждого type) Статьи только *link* то Этот единственный факт решает все ниже.
| Звони. | Что он делает |
|---|---|
GET /galleries | live galleries из shop, новый first, items включенный; страничка — ?page.number=&page.limit= (1..100), first 50 без безвозвратно params; Ответ несет total и currentPage рядом с result[] |
GET /galleries/{id} | Одна галерея с ее items |
POST /galleries | Создать галерею вместе с ее items |
PUT /galleries/{id} | частичное обновление: name and/or items |
DELETE /galleries/{id} | мягкий удаление; галерея в использовании cannot быть deleted |
GET /galleries/{id}/usage | кто использует: products, categories, статьи, variant sets |
{ "id": "31", "name": "Blue", "source": "images", "canvas_id": "0",
"items": [
{ "item_key": "8231acd9-…", "kind": "image",
"file": { "id": "<file_id>", "url": "…" }, "poster": null,
"html": "", "caption": "front" },
{ "item_key": "4fba917d-…", "kind": "html", "file": null,
"html": "<b>Assembly</b>", "caption": "" } ] }Сервер решает, что role. kind Он был получен из file's type — image | video | audio. Единственный role ты set Рукой - это "html", который не имеет file. А. kind который не согласен с тем, что file это отказываться, не тихо fixed: 400 GALLERY_ITEM_KIND_MISMATCH. измеряемый — kind: "video" на image file Безмолвное исправление приведет к витрине магазина, которая не будет Соответствовать тому, что вы отправили.
Связывание одинаково для всех трех владельцев и следует за categories_replace pattern:
PUT /products/808 { "item": { "gallery_ids": ["31","32"], "gallery_ids_replace": true } }
PUT /categories/85 { "item": { "gallery_ids": ["31"], "gallery_ids_replace": true } }
PUT /articles/12 { "item": { "gallery_ids": ["31"], "gallery_ids_replace": true } }не-empty Список заменяет собой set в order Дано (дано) empty Список без Флаг означает "не трогать"" (Так что копировать GET body входить в PUT безопасен); ан empty список с Флаг все размывает. content Из галереи есть редактировать через PUT /galleries/{id}, never через его owner — и там items следует за тем же rule, с его own флаг: items: [] плюс items_replace: true Очистить галерею, а без флага последний item cannot вообще удаляется.
Последствия для вас cannot design вокруг:
- Одна галерея может сидеть на многих объектах.. Измерение: Галерея 9
usageс двумя products и один variant set Редактирование меняет каждый card Это показывает, что это точка, и это также ловушка.; - а variant set Ссылки на любые live галерея shop, Не только в галерее его own product (
PUT /products/{id}/variants,items[].gallery_id). Это Это поддерживаемый способ "взять галерею от другого" product". Галерея deleted Позже в чтении читать"0"И витрина падает обратно в объект's first галерея; - `galleries` может быть длиннее, чем `gallery_ids`. Галерея А variant set Точки возвращаются в card Даже когда product Сам по себе не link Прилагается в конце
galleries, умышленно вычеркнутый изgallery_idsЧтобы эхо отражалоGETbody обратно вPUTcannot прикрепить сосед's Галерея. Прочтите link set изgallery_idsи look content поднимать вgalleriesчерез id, never через position. The preview fallback считать связанный galleries Только: а product Галерея, которая приходит только через variant справочные ответыpreview: null,preview_auto: false. измеряемый; - Галерея в использовании cannot быть deleted:
400 GALLERY_IN_USE: used in N places. не связываться first (gallery_ids: []+gallery_ids_replaceНа собственников,gallery_id: "0"на sets), проверитьGET /galleries/{id}/usage, затем удалять; - список never перевозить galleries.
GET /productsне допускать fields полностью иGET /categoriesвозвращает их обратно empty. Оба они несутpreview, вычисленный как first image item из first Галерея, когда объект не имеет preview из его own. Читать galleries от card Конечные точки только.
source: "canvas" (с canvas_id ≠ 0) означает items Водитель: Canvas document и те, которые вы отправляете, хранятся в качестве резервной копии, пока вы не отключите связь. это mode на и посылать items В том же запросе ответы 400 GALLERY_MANAGED_BY_CANVAS; а document Уже ездят по другой галерее ответы 400 GALLERY_DUPLICATE_CANVAS. Все измеренные.
Спросить card для меньшего количества кадров
А. product Сфотографированный в 360° для дюжины цветов несет дюжину galleries из 35-36 кадров. card конечные точки return Все они и а page показывает точно Один из них - мера live shop: GET /products/by-slug/… был 159 KB, который 138 KB (87%) было galleries 91% из них не используется на каждом render.
Оба card конечные точки — GET /products/{id} и GET /products/by-slug/{slug} — взять optional прогноз:
galleries= | Что возвращается |
|---|---|
отсутствующий или full | Каждая галерея с каждым item — Ответ: байт за байтом, что это always был, Ничего из того, что вы уже написали, не меняет |
cover | Каждая галерея держит его first item только |
active | тот показанный Галерея хранит все свои items, Каждая галерея сохраняет свои cover |
active Необходимо знать, какой set тот page Показать: Pass variant=<sku> (или set's числовой id как струна когда она sku это empty) — то же самое key тот Витрина вставляет ?v=. Выброшенный или указывающий на set которого больше не существует, Сервер принимает first active set, И когда это set Нет галереи его own, тот product's first связанный Галерея — то же самое rule витрина магазина применяется, поэтому card и page never Непонятно, какая галерея будет показана.
Каждая галерея несет items_total — Сколько items имеет before тот Проекция. Прочитайте счетчики ("1 из 36") от него, а не от items.length, который В рамках проекции 1 для galleries Вы не просили.
Ничего. else Переезды: список galleries, его order, gallery_ids, тот preview fallback и все остальные field из card идентичны во всех трех режимах. Ан unknown value ответы 400 GALLERIES_PROJECTION_UNKNOWN Вместо того, чтобы молчать отступать назад к full — "I Прошел параметр и ничего не стало легче" - это Одна неудача, которую вы не могли отладить.
# a grid of colour swatches: 13 covers instead of 432 frames
GET /products/by-slug/audi-rs-7?company_id=14&galleries=cover
# the page of one colour: that gallery in full, the rest as covers
GET /products/by-slug/audi-rs-7?company_id=14&galleries=active&variant=rs7-blue
# the buyer clicked a swatch and you want its frames without reloading the card
GET /galleries/31GET /galleries/{id} ничего не знает о проекциях и always Ответы с помощью тот full set из items — Это способ заполнить галерею, которую вы получили в качестве cover.
Коды ошибок: GALLERY_NOT_FOUND (404 — один и тот же ответ для кого-то else's Галерея и для одного, что never существовал), GALLERIES_PROJECTION_UNKNOWN, GALLERY_IN_USE, GALLERY_NAME_REQUIRED / GALLERY_NAME_TOO_LONG, GALLERY_TOO_MANY_ITEMS, GALLERY_DUPLICATE_ITEM_KEY, GALLERY_ITEM_KIND_MISMATCH, GALLERY_ITEM_FILE_REQUIRED, GALLERY_ITEM_HTML_REQUIRED, GALLERY_ITEM_HTML_TOO_LONG (item html более 64 KB), GALLERY_FILE_NOT_FOUND (404), GALLERIES_TOO_MANY, GALLERY_DUPLICATE_LINK, GALLERY_MANAGED_BY_CANVAS, GALLERY_DUPLICATE_CANVAS, CANVAS_DOCUMENT_NOT_FOUND (404), CANVAS_TYPE_MISMATCH. Все, кроме самого 404 ответов 400 — FailedPrecondition На карте 400, а не 412..
8.Письмо.
- Цены – это целые единицы shop'Валюта s. Над create/update, Опущенный, пустой или только белое пространство
price.currencyиспользует shop'Валюта s. непустотный value должен соответствовать ему (случайно-чувствительный, окружающее белое пространство обрезано)), в противном случае API возврат400 CURRENCY_IS_STORE_LEVEL. Никакого преобразования не происходит. Ответ несет в себе shop'Валюта не может быть Меняется с помощью токена — если price Список несогласных сstore.currencyизGET /v1/account/token, Остановитесь и спросите owner; category_idЭто первичный category,categories[]Дополнительные; на Редактировать непустоеcategoriesзаменять дополнительный set (не добавляет), и[]очищается только сcategories_replace: true;catalog_hidden,comments_enabled,questions_enabledявляются трехгосударственными струны (""/"true"/"false");is_publishedНастоящий булеан;orderна category Создание игнорируется — новый раздел идет последним; order это set черезPOST /categories/move;- тот main image это
preview: {id}в создании,preview_id: "…"редактировать (гдеpreview: {id}принимается в качестве псевдонима); PUT /products/{id}является частичным, за исключениемprice(пишетold_priceи тоже) иseo(переписыватьog_image_id,noindex,nofollow); Рецепты для массовых правок, принимать off продажа, копирование, удаление, наборы и перемещение каталога находятся в/docs/catalog-import§12;- galleries связаны, а не встроены:
gallery_ids[](+gallery_ids_replace) на owner, контент черезPUT /galleries/{id}— См. § 7; - а category's images Должно быть, твой own Live файлы.
preview_idиseo.og_image_idнаPOST /categoriesиPUT /categories/{id}проверяют для владения: чужой или удаленный file ответы 404 `CATEGORY_FILE_NOT_FOUND` — один code В обоих случаях ответ никогда не Подтверждает, что иностранный file существует. Это Изменение поведения: before Эта волна является category Пишите с иностраннымpreview_idТолько через это прошли. поля, которые вы отправляете, проверяются, поэтому переименование поля category Не нужны никакие СМИ. Семья одна:PRODUCT_FILE_NOT_FOUND,ARTICLE_FILE_NOT_FOUND,GALLERY_FILE_NOT_FOUND.
Проверяйте каждую запись, читая объект и сравнивая его. fields ты посланный. 200 доказывает только то, что запрос разобран; § 10 перечисляет, что 200 может быть использовано для анализа. тихонько.
Перезагрузка целого каталога — `POST /products/import`. Доверить 5 000 rows по запросу и a row несёт *целое* product: attributes через code, variant топоры и sets, и галерея order как список уже загруженных file_ids. Идемпотенция key это sku: а row который находит свое product обновления это, а row Это не создает одного — нет местного article → id manifest необходимый
- для повторного запуска. fields keep их current value;
attributes,
variants и images заменить-sets с обычными *_replace флаги; fields вне договора (SEO, preview, сцена, groups, запаса) never
- Тронутый, ответ - HTTP 200 с вердиктом на человека row —
results[i]перевозить
row (1-основанный), product_id, action (created / updated / skipped) и errors[]; а skipped row писать ничего вообще, Потому что партия - это одна Сделка, каждая row Он валидируется против своего own контракт first — отрицательный price, слишком длинный field или display Словарь возвращается как ROW_INVALID на этом row с обидчиком field, Соседи являются Все еще написаны.: empty или более длительный items (400), а dev-contour key (403 PAT_CONTOUR_MISMATCH — масса import пишет в live shop Только и только, и key's contour Отклонить призыв before хозяин, а body более 64 MiB (413 REQUEST_TOO_LARGE — со 100+ attributes за product keep партии при ≤1 000 rows), минута row Бюджетный бюджет (429) IMPORT_RATE_LIMITED) и план's product Потолок (429) PRODUCT_LIMIT_REACHED, пересчитанный над партия's new rows). Изображения идут в import's own галерея — created один раз, повторно использовано после этого и never Галерея для всех else использования, в том числе и a link это Пока что живет только в dev draft; Неприкосновенность и неприкосновенность import создает его own Рядом с ним. import's Галерея берет first место среди них тот product's ссылки когда это created; ан order ты set Рукой - это never В галерее, где он был позже, import Владеет set из file_ids и их order Только — подписи, плакаты и html items Выживают на кадрах, которые Изменяя видимость клиента content утвержденного совместного product Отправляет его обратно в модерацию, точно так же, как обычное редактирование.. POST /products/batch Неизменный и правильный для краткого списка plain cards.
Полное руководство по рецептам и error коды: GET /docs/catalog-import.
8.1.Статья author (news)
POST /articles (item.author) и PUT /articles/{id} (item.author) принимать тот article's редакционный author. Это resource data, Не разделяемый template author. The news header только отдавать Article.author. created_by/updated_by Остается аудит fields и являются never Используется как public линия.
{"item":{"author":{"mode":"manual","name":"Анна Иванова","description":"Редактор","photo_id":"11111111-1111-4111-8111-111111111111"}}}mode: "member": пройтиuser_idиз одного active член твой own shop с помощью roleowner,adminилиeditor. Сервер хранит a snapshot из name построенный из account's first и последний name, Возвращение к имени пользователя; тот email это never использованный. name В запросе отправляется ignored в этом mode.mode: "manual":nameэто required (after обрезка белого пространства), максимум 255 персонажи.descriptionявляется optional caption, максимум 500 персонажей.- А. photo это optional в обоих режимах:
photo_idЭто UUID из одного live библиотека file твоей own shop и current contour; тот account Аватар не копируется автоматически. file URL Складывайтесь, только для чтенияphoto: MiniFileэто resolved По данным сервера. authorотсутствующий в PATCH — существующий author Присланный, сохраненный.authorполностью заменяет строку;{"mode":"none"}удаляет author и photo.- Изменение профиля или удаление члена не переписывает сохраненное авторство. несвязанный PATCH Не перепроверяйте старое членство.
- Публичные чтения раскрывают только
name,description,photo:mode,user_id,photo_idне возвращаются. нет author профиль link или page. - Недопустимые или иностранные члены:
404 ARTICLE_AUTHOR_NOT_FOUND; пропавшийuser_idчлен mode:400 ARTICLE_AUTHOR_MEMBER_REQUIRED; empty manual name:400 ARTICLE_AUTHOR_NAME_REQUIRED; иностранный, deleted или draft-только photo в prod:400 ARTICLE_FILE_NOT_FOUND.
Список команд для выбора author в admin является существующий GET /v1/orgs/{id}/members, доступный для owner/admin. Быть достойным как author не расширяет член's Доступ к управлению командой. PAT Интеграция может посылать уже известный user_id Под обыкновенным article-Разрешение на редактирование; нет new scope и нет public staff Список есть created.
8.2 Сравнение продуктов
Когда shop Поворачивает сравнение, его витрина получает весы button на product cards и на этом product page, "Сравнение" item в покупателе's account menu, а badge С прилавком в standard header и page /compare, где Покупатель ставит на max_items products одного section side через side. Вне дома default: а shop это never Спасенный settings иметь none из этого и /compare Остается обычный каталог address. Витрина магазина читает settings на всех page (Посмотреть конец этого section), Изменения появляются в один раз.
Один рекорд на shop, взятый из key — Нет никакого company_id в любом месте.
| Звони. | Сфера охвата | Магазин role | Что он делает |
|---|---|---|---|
GET /compare/settings | catalog:read | любой член | тот settings и ссылка, которую рисует экран шкафа |
PATCH /compare/settings | catalog:write | owner, admin | изменениях, fields Вы посылаете, ответы как GET |
PATCH /compare/settings
{ "expected_version": 3, "enabled": true, "max_items": 6,
"excluded_group_ids": { "ids": [27] } }Поле (settings.* В ответе) | значение | В PATCH |
|---|---|---|
enabled | Сравнение находится на | optional |
header_link | тот header badge; never Без шоу без enabled | optional |
max_items | products В одном сравнении, внутри max_items_min…max_items_ceiling (2…10) | optional; Зажатый, не отказано — прочитайте его обратно |
excluded_group_ids | характерный groups полностью | {"ids": [...]} Заменяет список, {"ids": []} Проясняется, отсутствует key держит его |
excluded_attribute_ids | Отдельные характеристики остались | та же самая обертка |
version | 0 пока shop Нет рекордов | Отправьте обратно как expected_version — required |
updated_at | Последнее изменение; отсутствует при дефолтах | — |
недостатки: enabled: false, header_link: true, max_items: 5, Никаких исключений, version: 0.
- частичный. А. field Вы не отправляете его value; Запрос, который меняет Ничего не пишет и ничего не держит
version. - Исключение ids сужается до live groups и характеристик этого shop; иностранный, deleted и unknown ids Упаковка производится без error (≤ 5 000 Сравните то, что вы отправили, с тем, что было возвращено..
- The settings не иметь draft Копия: письмо меняет live витрина сразу.
Ссылка в обоих ответах: groups[] — тот shop's характерный groups через sort_order (label Сырой i18n JSON, name Готовы показать, каждый с attributes[] в спецификации order, Так что характерная черта в groups из двух Спецификация в обоих; ungrouped[] — качеств, выходящих за пределы каждого Каждый признак несет в себе card_visibility: public, audience (только часть покупателей или hidden. А. hidden характерный never Сравнение, независимо от того, что вы exclude или include — Сравнительные исследования Только то, что видит покупатель на card (§8.3. Почему это так? hidden (hidden_reason: not_configured, staff_only, card_off, api_off, audiences_off, audience_inactive) и который audience Видит это (audience_id, audience_name) приходить owner/admin только.
| Ответить | Когда |
|---|---|
409 COMPARE_SETTINGS_VERSION_CONFLICT | expected_version несвежий, или 0 Пока запись существует: прочитайте еще раз и повторно подавайте заявку |
400 COMPARE_SLUG_CONFLICT | Включается, пока shop имеет live section с помощью slug compare В корне этого tree или прямо под домом page — /compare был бы cover Отправляйся с "confirm_slug_conflict": true Только если бы owner согласованный |
400 INVALID_REQUEST | голый массив вместо {"ids": [...]} |
| 400 (проверка) text) | more более 5 000 ids В списке - отрицательный expected_version |
403 COMPARE_SETTINGS_FORBIDDEN | PATCH с помощью editor; любой призыв кого-либо, кто не является членом key's shop |
403 PAT_SCOPE_MISSING | тот key недостаток catalog:read / catalog:write |
403 PAT_CONTOUR_MISMATCH | PATCH с а dev-contour key, отказываться before обработчик |
400 DEV_MODE_UNAVAILABLE | PATCH из кабинета в draft mode |
Что читает витрина: GET /v1/storefronts/resolve перевозить compare: {enabled, header_link, max_items, excluded_group_ids, excluded_attribute_ids}; null средство off. The public спецификация GET /categories/{id}/spec Каждый дарит каждому item его char_group_name, так Сравнительная таблица сгруппирована точно так же, как и settings вкладка. /compare?ids=… это индексируемый, когда он имеет 2…max_items products одного section; Его канонический Список ids восходящий order. не находится в карте сайта, и robots.txt перевозить Clean-param: set /compare (set только маркирует покупателя's own Сравнение. header и footer Они спроектированы так же, как и любые другие system page: resource_type:"system", id 12 (/docs/chrome).
8.3. кто видит характерные признаки
настройка characteristics Up — типы, options, фильтров в a section, groups, шаблоны и section values — это GET /docs/characteristics; это section Они покрывают только свои visibility.
The {id} является атрибутом id: Найди его code в GET /attributes (тот key Выберите shop, не company_id query) (каждый item иметь id и code) или в groups[].axes[].attribute_id Ответ на коллапс.
Каждая характеристика имеет свою own покупатель visibility, и product page, Списки, обращенные к покупателю API И все они повинуются ему. shop's owner или администратором, в том числе через PAT с catalog:write. The scope не обходить стороной owner/admin Проверка членства.
| Звони. | Сфера охвата | Ответить |
|---|---|---|
GET /attributes/{id}/access | catalog:read | {attribute_id, access} |
PUT /attributes/{id}/access | catalog:write | то же самое, after писать |
PUT /attributes/412/access
{ "expected_version": 0,
"access": { "visibility": "public", "show_storefront_card": true,
"show_storefront_list": true, "show_admin_product": true,
"show_admin_list": true, "show_api": true, "show_export": true } }visibility:public— Каждый покупатель;audience— Только покупателиaudience_id, ан active audience из этого shop, И только в то время, когда shop иметь Аудитория переключается на;staff— Никто вне shop's команда.audience_idсходить сaudienceтолько.- Где это показывает:
show_storefront_card— тот product page и сравнение;show_storefront_list— listing cards;show_api— покупатель лицом API тот Витрина читает, так что поверните его off Скрывает характерные черты от покупателей повсюду;show_admin_product,show_admin_list— кабинет;show_exportхранится, ничего не читается для характеристик. - `PUT` Заменяет весь рекорд: Флаг, который вы оставляете, становится
false. Начать сGETи всех их послать. expected_versionэтоaccess.versionизGET. Характеристика never Сэкономленные ответыvisibility: "staff",version: 0— а new характерный оставаться hidden от покупателей до тех пор, пока не будет сохранен доступ к; тот firstPUTидти сexpected_version: 0.- Перед использованием attributes или group оси в a custom список, Прочитайте свой доступ, затем PUT Полный рекорд с
visibility: "public",show_api: true,show_storefront_list: true, - держать другие флаги, как и предполагалось. keepshow_storefront_card: falseчтобы оставить product page без изменений. Видимые оси, коллапс молча возвращается plain Плитка; а group Только один покупатель-видимый участник также является plain плитка (§4.1)). - Filter side effect: ан
is_filterableАтрибут, видимый на поверхности списка Также может появляться в category filter Проверь, что там. section after открытие Доступ; список visibility не является а card-Только косметический переключатель. - Ошибки: 404
ATTRIBUTE_NOT_FOUND(не это shop's, deleted, илиaudience_idне существует, 409ATTRIBUTE_ACCESS_VERSION_CONFLICT, 400 для недействительной комбинации (audienceбезaudience_id,audience_idсpublicилиstaff, неактивный audience), 403CUSTOMER_ACCESS_FORBIDDENдля одного editor.
9.Что за хрень? data Может не сказать вам
Магазин kiberpank имеет category "Акция" (id 109) публикуется, показана в тот menu и empty: count_products: 0, filter.category_id=109 → total: 0. В том же пробеге GET /promotions возвращенный {"result": []} и каждый product перевозимый promotion_name: "" с old_price: 0.
В ходе исследования было установлено, что платформа не promotions вообще. Это было неправильно.. Промоакции - это реальное предприятие — CRUD в /promotions, /promo-keys и /cart/promo, Закрытый воротами PAT scopes promotions:read / promotions:write. А. key без scope получать PAT_SCOPE_MISSING, не является empty Список, так [] это Реальный ответ: *это shop Не устанавливаются никакие правила*. Пустота была прочитана как факт. О платформе — то же самое class Недостаток этого page О том, что сделано во время документировать его.
The rule: Когда задача зависит от свойства data чье существование вы cannot Проверяйте, спрашивайте owner. Не курируйте "Продажа"" row Рука и рука его над как то, что было предложено. document: promotions.md.
10.Безмолвно ignored
| Что? | Что на самом деле происходит |
|---|---|
а filter без filter. префикс | 200 с полным каталогом |
неизвестный filter.* (e.g. filter.slug) | 200 с полным каталогом |
price.currency писать | подтверждено, не проигнорировано; см. § 8 для валютного контракта |
company_id В письменном виде body | неуважение; shop исходит из key |
order Когда создается category | Проигнорировано; раздел идет последним |
catalog_hidden на POST /products | падение; set с ним PUT /products/{id} after создавать |
stock_quantity на PUT /products/{id} | Сброшено; запас PUT /products/{id}/stock, Закрытые ключи |
categories: [] Редактировать без categories_replace | Читать как «Не меняйся», «Чисто»" |
gallery_ids: [] без gallery_ids_replace | Правило: читать как «не меняйся», а не «отключи все»" |
items: [] В галерее без items_replace | Читать как «Не меняйся»; для очистки нужен флаг |
items В галерее, которая canvas_id ≠ 0 | Сброшенный; сохраненный items Оставайтесь в качестве резервной копии, пока вы не отсоедините документ |
читать galleries / gallery_ids из GET /products или GET /categories | отсутствующий или пустой design — Списки несут preview только |
11. Пределы
page.limit1–100,page.numberиз 1;filter.idsmax 100;attribute_filtersmax 20 ввод,valuesmax 100 за въезд;- шириной 32...2560, качество
?q=40..95; - галерея держит ≤24 items, его name это 1.64 Персонажи, объект links ≤15 galleries;
POST /products/batchОтветы на каждый item: 200 не означает, что каждый item приземленный — И это единственный path возвращаетGALLERIES_TOO_MANYКак машина code, Потому что однообъектные призывы отвергают слишком длинныйgallery_idsс помощью валидатор own message первый;POST /products/importбрать ≤5 000 ряды, и на row ≤500 attributes, ≤20 вариант осей, ≤500 наборы вариантов, ≤24 images (тот Потолок обычной галереи — шире import Построить галерею The нормальный editor Отказывается спасать), ≤64 теги; также отвечает на row, и Это предупреждение действует.GALLERIES_TOO_MANYза row И когда это product Максимальный размер 15 galleries.
12.Как это было проверено
- 2026-08-19, витрина против
api.vizen.shop(Компания 11. number в §3–§6 и §9 переоценены live сcurl2026-08-20: The 426/60/426 filter Тройной,filter.idsПара 400-vs-200, границы связок,/categoriesформа решетки,403асимметрия, и оба image sizes из то же самое file (оригинальное хранилище vs/w/320/webp/…, плюсHEADиGETна идентичный URL). - 2026-08-30, galleries (§7) измеренный с помощью
curlВзлеты на волне 3 на live База данных: шесть/galleriesмаршруты;kindполученный из file type иkind: "video"на image с отказомGALLERY_ITEM_KIND_MISMATCH;GALLERY_ITEM_FILE_REQUIRED,GALLERY_ITEM_HTML_REQUIRED,GALLERY_NAME_REQUIRED,GALLERY_FILE_NOT_FOUND(404),GALLERY_NOT_FOUND(404 для одного id Которого не существует),GALLERY_DUPLICATE_LINK,GALLERIES_TOO_MANY(только партия),GALLERY_MANAGED_BY_CANVAS,GALLERY_DUPLICATE_CANVAS,CANVAS_DOCUMENT_NOT_FOUND,CANVAS_TYPE_MISMATCH; Одна галерея, связанная с product, а category и ан article сразу, плюс а variant set, иGET /galleries/{id}/usagelisting Все они;DELETEНа него ответитьGALLERY_IN_USE;gallery_ids: []Без флага, который ведет себя как безопс;preview_auto: trueс помощью preview Позаимствовано из галереи; то же самое product Читать назад →GET /productsгрузpreviewно нет galleries. После этого тестовые объекты были удалены.. - Подробности договора читайте в
backend-3D/api/catalog/catalog.proto; ласточка механизм вpkg/queryparserи грпк-gateway's default ПАРТЕР; ПАРТЕР width лестница вinternal/api/imgserve/imgserve.go. Продвижение в одном и том же прото (B5 RPC) иinternal/core/services/guard.go;GET /promotionsбез Токен отвечает на 401 — доказательство того, что маршрут live. - 2026-09-25, сравнение (§8.2) и характерный доступ (§8.3): не измерено live. Управляющие были настроены против одноразовых почтовых ящиков с каждым Миграция (интеграционные тесты)
TestCompareSettings*,TestCompareResolveStorefront); тот HTTP формаPATCH /compare/settings, Включая 400 для голого массива, через реальный gateway (TestCompareSettingsRoutes). §8.3 читать изattribute_field_access.goи его хранители.