Vizen Shop

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 на pageproductListing widget — widgets.mdlive data, look ограниченный props
а product row в твоем own designown разметка над live feed products[] — own-markup.md §2live цены; выбрано attributes и group Цены замораживаются только в случае ввода в разметку
один card, Множество комбинацийvariants внутри одного productодин URL, не разделять SEO
а URL и content в комбинациисвязанный products в groupН. products плюс group поддерживать

Когда variant должен быть look Выберите один из трех — они не заменят друг друга:

Ты хочешьМеханизмГде это написаноСтоимость
другой чип и телега-row photo, Один общий слайдертот set's image_idPUT /products/{id}/variantsОдин снимок на каждый set
Различные фотографии в слайдере, один product, один URLГалерея плюс set's gallery_idPOST /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_idproducts одного section
filter.include_subtreeс category_id: это section и каждый section под ним гнездится
filter.idsнесколько products сразу — Повторить параметр
filter.queryПоиск по name
filter.is_publishedопубликовано только
filter.min_price / filter.max_priceprice range
filter.company_idкоторый shop (Анонимные чтения; с токеном key решать)
page.number / page.limitpaging, 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_codesselected 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

Рецепт — постройте переключатель из одного ответа:

  1. Для position p найти groups[] с group_id == p.group_id (compare как струны).
  2. Член's full card является элементом result.items или group_members чей id равный члену's product_id. Нет. card — Откройте член своей url вместо.
  3. Чип оси a для value v: Член, который values равный current член's на каждой оси и v на a. Нет — комбинация не Есть: Показать инвалид, never заменить другой SKU. Члены редкого group может быть недостижимым с помощью одноосевых чипов — предложить «все» variants" список.
  4. Члены делают не Выполняйте просьбу'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 line

The 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-level company_id и ответы 403 COMPANY_ID_PROBLEM для префиксированных form. Измеряют оба;
  • `category.id` это number и `category.parent_id` Это веревка (107 vs "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: true first, шоу пользователь 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, не Цифры: они есть int64 fields, и прото3 JSON рендеринг int64 как веревка. "0" в variant's gallery_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=menu
view=Что возвращается
отсутствующий или 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 предок)
previewMiniFile компоненту
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 domain

The /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 /gallerieslive 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 Чтобы эхо отражало GET body обратно в PUT cannot прикрепить сосед'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/31

GET /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 с помощью role owner, 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/settingscatalog:readлюбой члентот settings и ссылка, которую рисует экран шкафа
PATCH /compare/settingscatalog:writeowner, 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 Без шоу без enabledoptional
max_itemsproducts В одном сравнении, внутри max_items_min…max_items_ceiling (2…10)optional; Зажатый, не отказано — прочитайте его обратно
excluded_group_idsхарактерный groups полностью{"ids": [...]} Заменяет список, {"ids": []} Проясняется, отсутствует key держит его
excluded_attribute_idsОтдельные характеристики осталисьта же самая обертка
version0 пока 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_CONFLICTexpected_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_FORBIDDENPATCH с помощью editor; любой призыв кого-либо, кто не является членом key's shop
403 PAT_SCOPE_MISSINGтот key недостаток catalog:read / catalog:write
403 PAT_CONTOUR_MISMATCHPATCH с а dev-contour key, отказываться before обработчик
400 DEV_MODE_UNAVAILABLEPATCH из кабинета в 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}/accesscatalog:read{attribute_id, access}
PUT /attributes/{id}/accesscatalog: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 от покупателей до тех пор, пока не будет сохранен доступ к; тот first PUT идти с expected_version: 0.
  • Перед использованием attributes или group оси в a custom список, Прочитайте свой доступ, затем PUT Полный рекорд с visibility: "public", show_api: true, show_storefront_list: true, - держать другие флаги, как и предполагалось. keep show_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 не существует, 409 ATTRIBUTE_ACCESS_VERSION_CONFLICT, 400 для недействительной комбинации (audience без audience_id, audience_id с public или staff, неактивный audience), 403 CUSTOMER_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.limit 1–100, page.number из 1; filter.ids max 100;
  • attribute_filters max 20 ввод, values max 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 с curl 2026-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}/usage listing Все они; 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 и его хранители.

Исходник: https://api.vizen.shop/docs/catalogue?full=1

Работает в демо-режиме