Vizen Shop

Vizen API — быстрый старт

/docs/quickstartcurrentRU· проверено 2026-09-10

Резюме. Практический гайд для внешнего разработчика или ИИ-агента: выпустить ключ, прочитать его паспорт, завести карточки товара, загрузить картинки, не упереться в лимиты и сдать работу владельцу. Всё — по реальному контракту api.vizen.shop; источник правды — proto в api/*/*.proto и схема /openapi.json. Это ЕДИНСТВЕННАЯ редакция быстрого старта: она же отдаётся бэком по GET /docs/quickstart и показывается на сайте vizen.shop/docs/quickstart.

Status: current · Verified: 2026-09-10, сверено с кодом (domain/api_scopes.go, domain/api_access.go, лимиты cmd/core/main.go, ручки api/*/*.proto) · Owner: backend · Serves: GET /docs/quickstart

0. Что ещё читать (машиночитаемая поверхность)

АдресЧто это
GET /docs/index.jsonиндекс всей документации: путь, заголовок, зачем читать, статус
GET /llms.txtпорядок чтения для ИИ-агента — начинать отсюда
GET /v1/account/tokenпаспорт вашего ключа (см. §1.3)
GET /docs/skillsплатформа, объяснённая по путям работы; начинать с vizen-start
GET /openapi.jsonполная OpenAPI-схема (то же на /v1/openapi.json, /compiled.swagger.json); GET /partner/openapi.json — только то, что открыто ключу
GET /docs/scopes.jsonвсе права ключа, с пометкой чувствительных — генерится из кода
GET /docs/events.jsonвсе события вебхуков и параметры доставки — генерится из кода
GET /docs/catalog-importгайд наполнения каталога: категории, характеристики, варианты, склейки
GET /docs/webcodingгайд вёрстки страниц магазина кодом
GET /docs/vz-keysключи vz-: своя вёрстка на данных магазина
Области (/docs/catalogue, /docs/widgets-area, /docs/own-markup, /docs/chrome, /docs/pricing, /docs/promotions, /docs/orders, /docs/stock, /docs/webhooks, /docs/troubleshooting)поведение, которое не описывается одной ручкой
GET /карта корня API в JSON

1. База и аутентификация

  • База API: https://api.vizen.shop
  • Ключ (PAT): заголовок Authorization: Bearer vz_pat_<…> в каждом запросе.
  • Где выпустить: кабинет магазина → «API-доступ» (https://admin.vizen.shop/api-access) → «Выпустить токен». Ключ показывается один раз — скопируйте сразу.
  • Права (scopes) отмечаются при выпуске, до 32 на ключ. Существующий ключ новые права автоматически не получает.

1.1 Права

Полный машинный список — GET /docs/scopes.json. Человеческая таблица:

scopeчто открывает
catalog:readчтение товаров/категорий/характеристик + владельческий вид: черновики, скрытые категории, очередь модерации отзывов, дев-контур ключа. Без него публичные ручки отвечают как анониму
catalog:writeсоздание/правка товаров, категорий, характеристик
orders:read / orders:write ⚠️заказы (ПДн покупателей)
stats:readстатистика
storage:readчтение медиатеки
storage:writeзагрузка файлов (нужно для картинок)
domain:read / domain:writeсвой домен магазина
promotions:read / promotions:writeакции, купоны, наборы
pricing:read / pricing:write ⚠️ценовые правила по количеству (закрытая коммерческая операционка)
customers:read / customers:write ⚠️клиентская база и покупательские группы (ПДн)
access:read / access:write ⚠️политики доступа к закрытым ресурсам, парольные выдачи
publish:write ⚠️публикация черновика на живой сайт — галочка «Разрешить публиковать черновик» при выпуске ключа-черновика
forms:read / forms:writeконструктор лид-форм (схемы полей)
leads:read / leads:write ⚠️заявки покупателей. ПДнcatalog:* и forms:* их не открывают

⚠️ — чувствительное право: выдаёт только владелец или администратор магазина. Сотрудник с ролью редактора такой ключ выпустить не может, а при понижении роли уже выданные ключи с этими правами отзываются автоматически. Рабочие права (каталог, медиатека, акции, статистика, домен, формы) этим гейтом не закрыты.

Для «завести каталог с картинками» нужны минимум `catalog:write` + `storage:write`catalog:read для проверки).

1.2 Префиксы путей (неоднородность, знайте заранее)

  • Каталог (товары, категории, атрибуты, акции, заказы) — без /v1: POST /products, GET /products/{id}.
  • Хранилище / аккаунт / организация — под /v1: POST /v1/storages/files, GET /v1/account/token, GET /v1/orgs/current.

1.3 Первый запрос — паспорт ключа

Делать это до первой записи:

curl https://api.vizen.shop/v1/account/token \
  -H "Authorization: Bearer vz_pat_ВАШ_КЛЮЧ"
  • 200 → ключ рабочий. В ответе:
    • contour и writes_to_live — уйдут ли правки сразу на живой сайт. prod пишет на витрину, dev — в черновик, который публикует человек. Контур — свойство самого ключа: заголовком или company_id не переключается. Если writes_to_live: true — предупредите владельца до массовой записи.
    • store.currency — валюта магазина. Цена товара пишется целым числом в ней; присланная price.currency, не совпавшая с магазином, отклоняется — 400 CURRENCY_IS_STORE_LEVEL. Сменить валюту ключом нельзя.
    • store.storefront_url — адрес витрины, где проверять результат (учитывает подключённый свой домен). Не угадывайте его.
    • capabilities — что ключ реально может. Верить нужно этому, а не набору scopes: наличие тематического права не значит, что метод открыт ключам.
    • warnings[] — передать владельцу целиком.
    • guides — адреса гайдов для этого ключа.
  • 401 → неверный или просроченный ключ.
  • 403 `PAT_SCOPE_MISSING` на приватных ручках → нет нужного права (в тексте ошибки — required_scope). 403 `PAT_METHOD_NOT_ALLOWED` → метод закрыт для ключей вообще (настройки витрины, члены компании, сами ключи и вебхуки) — это делает человек в кабинете.

Чтение каталога для проверки ключа не годится: публичные ручки отвечают 200 и без catalog:read — проекцией анонима (только опубликованное, без черновиков), так что «200 на /products» ничего не доказывает.

Дальше по знакомству: GET /categories (узнать category_id), GET /products/{id} (одна карточка целиком).

2. Карточка товара

Создать — POST /products

{
  "item": {
    "name": "Кровать двухъярусная Аврора 120×90",
    "description": "Массив сосны, цвет Сэнди",
    "price": { "price": 45892, "currency": "RUB" },
    "category_id": 12,
    "sku": "AVRORA-120-90-SANDY",
    "tags": ["кровати", "детская"],
    "seo": { "slug": "avrora-120-90-sandy" }
  }
}
  • price.priceцелые единицы валюты (рубли, не копейки), валюта — магазина (§1.3).
  • category_id обязателен, чтобы товар показывался в разделе витрины. Это основная категория — от неё строится канонический URL карточки.
  • categories (массив id) — дополнительные категории: товар виден и в их разделах, на URL не влияют. В PUT: поле не прислано — не трогается; прислано непустым — полная замена; снять все — "categories": [], "categories_replace": true.
  • slug живёт внутри `seo` (seo.slug) — это адрес страницы товара. Можно не присылать — сервер сгенерирует из name (транслитерация + дедуп). Ручной слаг с кириллицей/пробелами не отвергается — транслитерируется в [a-z][a-z0-9_-]* (итог виден в ответе). Отказы только для безнадёжных форм: чисто числовой → SLUG_NUMERIC_FORBIDDEN, не с буквы → SLUG_INVALID, служебные слова (cart, admin…) → SLUG_RESERVED.
  • sku — уникален в магазине среди неудалённых товаров (пустой — можно, в уникальности не участвует). Дубль → 409 already_exists; ответ пока не говорит, какое поле конфликтует (sku или слаг) — при импорте проверяйте занятость заранее. Занятый слаг в прод-контуре отвечает SLUG_TAKEN.

Получить / список

  • GET /products — список; фильтры — query-параметрами с префиксом `filter.` (?filter.category_id=12). Без префикса параметр молча проглатывается и возвращается весь каталог с 200 — сравнивайте total до и после. Подробно — /docs/catalogue.
  • GET /products/{id} — один товар со всеми полями (галерея, цена, рейтинг, варианты, остаток).

Редактировать — PUT /products/{id}

Partial по полям верхнего уровня: присылаете только то, что меняете.

{ "item": { "price": { "price": 43990, "currency": "RUB" } } }
⚠️ ГЛАВНАЯ ЛОВУШКА: вложенный `seo` заменяется ЦЕЛИКОМ. Пришлёте seo только с мета-тегами и без slug — slug обнулится, и у товара пропадёт адрес страницы. Правило: трогаете seo — сперва GET, поменяйте нужное поле, отправьте seo целиком (read-modify-write).

Удалить / опубликовать

  • DELETE /products/{id} — мягкое удаление (восстановимо 30 дней).
  • is_published: trueitem) — товар появляется в магазине сразу (в прод-контуре; в дев-контуре — после публикации черновика человеком).
  • is_shared — отдельный флаг «в общий маркетплейс» (с модерацией).

Импорт пачкой — POST /products/batch

Заводите каталог целиком — не гоняйте POST /products в цикле, шлите до 100 товаров за раз. Позиция в items — тот же объект, что item в одиночной ручке, и те же проверки.

{ "items": [
  { "name": "Кровать Аврора", "price": {"price": 45892, "currency": "RUB"}, "category_id": 12 },
  { "name": "Стол Лофт",      "price": {"price": 18900, "currency": "RUB"}, "category_id": 12 }
] }
⚠️ ЧАСТИЧНЫЙ УСПЕХ — разбирайте `results`, а не только HTTP-код. Ответ — 200, даже если часть позиций не создалась: одна кривая строка прайса не отменяет 99 хороших. Судьба каждой — в своём элементе results по index (позиция в items, с нуля).
{
  "results": [
    { "index": 0, "id": 511, "slug": "krovat-avrora" },
    { "index": 1, "error": "CATEGORY_NOT_FOUND", "message": "" }
  ],
  "created": 1,
  "failed": 1
}
  • Успех позиции: id > 0, error пустой. Провал: id = 0, error — код (PRODUCT_NAME_REQUIRED, CATEGORY_NOT_FOUND, CATEGORY_TYPE_MISMATCH, SLUG_TAKEN, already_exists для sku, PRODUCT_FILE_NOT_FOUND, PRODUCT_LIMIT_REACHED, INTERNAL).
  • Повторяйте только упавшие позиции: перезалив всей пачки создаст дубли слагов/sku у тех, что прошли.
  • Квота тарифа (products_max) считается на каждой позиции — пачкой потолок не перепрыгнуть; лишние вернут PRODUCT_LIMIT_REACHED.
  • Транзакция — на позицию, не на пачку: частично созданное остаётся созданным.
  • HTTP-ошибкой (не results) отвечают только «весь запрос негоден»: нет ключа, нет магазина, items пустой или длиннее 100.

3. Картинки — то, на чём спотыкаются

Картинка грузится не одним запросом с файлом, а через presigned-URL: сервер даёт временную ссылку, вы кладёте байты напрямую в хранилище, потом подтверждаете. Итог — UUID файла, который цепляется к товару.

  1. Попросить ссылку на загрузкуPOST /v1/storages/files (scope storage:write)

``json { "section": "content", "file_type": "image", "original_name": "avrora.jpg" } ``

- file_type — это категория, НЕ MIME: image | video | audio | model | file (мин. 3 символа). Пришлёте MIME (image/jpeg) — сервер сам нормализует в категорию, чтобы файл не выпадал из фильтров медиатеки.

- section — папка-раздел (мин. 3 символа); канон для фото товаров и картинок страниц — content.

Ответ:

``json { "result": { "id": "95fe4e96-5903-4dc3-873f-2d6e54de3047", "public_path": "/files/…" }, "upload": { "mode": "presign", "id": "<UPLOAD_ID>", "url": "<PRESIGNED_PUT_URL>" } } ``

> ⚠️ Два разных id. result.id — UUID файла (его цепляете к товару). > upload.id — идентификатор загрузки для шага 3. Их путают чаще всего.

  1. Залить байты напрямуюPUT <upload.url>, тело = сырые байты файла, заголовок Content-Type: image/jpeg (должен совпадать с типом файла). Это прямой PUT в хранилище без Authorization — ссылка сама авторизует.
  1. ПодтвердитьPOST /v1/storages/files/<UPLOAD_ID>/done (scope storage:write). Здесь используется upload.id, а не result.id.
⚠️ PNG/JPEG-оригиналы контента конвертируются в WebP. Для контент-секций (content, products) сервер на шаге /done конвертирует растровый оригинал в WebP q90 и не хранит исходный PNG/JPEG. id файла стабилен, но URL меняется (расширение станет .webp) — public_path из ответа шага 1 после /done может устареть. Правило: финальный URL читайте после /doneGET /v1/storages/files/{id}result.public_path. К товару файл цепляйте по id — там ничего не ломается.

Быстрый путь для импорта — картинка по ссылке

Если исходник уже лежит в интернете (выгрузка поставщика, старый сайт), три шага не нужны: POST /v1/storages/files/from-url (scope storage:write) — сервер скачает файл сам и вернёт готовый файл. На каталоге в пару тысяч фото это разница в часы.

{ "source_url": "https://cdn.postavshik.ru/foto/avrora.jpg",
  "section": "content", "file_type": "image", "original_name": "avrora.jpg" }
{ "result": { "id": "95fe4e96-…", "public_path": "https://…/avrora.webp" },
  "file_size": 148213 }
  • result.id — тот же UUID файла, что и в presign-пути: сразу цепляйте к товару. Никакого /done — конвертация в WebP и предгенерация нарезки уже сделаны, public_path финальный.
  • original_name можно не слать — возьмётся из адреса; расширение приводится к фактическому формату.
  • Форматы: jpeg | png | gif | webp | bmp | tiff; тип определяется по байтам, а не по Content-Type ответа. 3D-модели и прочие файлы — presign-путём.
  • ⚠️ Только публичные http/https-адреса. Внутренняя сеть (localhost, 127.*, 10.*, 172.16–31.*, 192.168.*, 169.254.*, ::1, fc00::/7) — 400 URL_NOT_ALLOWED; проверяется каждый хоп редиректа, их не больше трёх. Не скачалось / не картинка / не 200 → 400 URL_FETCH_FAILED; больше 10 МБ → 400 FILE_TOO_LARGE; таймаут — 30 с.

Прицепить к товару

Фото товара кладём в `gallery` (слайдер карточки), используя result.id:

PUT /products/{id}
{ "item": { "gallery": [ { "id": "<UUID1>" }, { "id": "<UUID2>" } ] } }

Миниатюру в каталоге система берёт из первого фото галереи. previewнеобязательное отдельное поле (особая промо-превьюшка): не заполняйте без нужды и не дублируйте в него фото из галереи, иначе на витрине оно покажется дважды. preview_id — только если нужна миниатюра, отличная от галереи; снять — "preview_id": "". Галерея по группам и варианты — /docs/catalog-import.

Как картинка отдаётся на витрине

  • Оригинал: по public_pathhttps://<магазин>.vizen.shop/files/…
  • Нарезка под ширину (быстрее, webp): https://<магазин>.vizen.shop/w/{ширина}/webp/<путь>/w/320/webp/… (миниатюра), /w/1600/webp/… (крупно).
  • Лестница ширин (предгенерируются и кэшируются — берите их): 32 · 64 · 128 · 320 · 640 · 1024 · 1600 · 2048 · 2560. Правило слота: ступень ≥ 2× CSS-размера (ретина). Произвольные ширины тоже работают (клэмп 16–4096), но режутся на лету — медленнее первого показа.
  • Качество: по умолчанию — политика ступеней сервера; переопределение — ?q=40…95.

4. Полный пример (bash): товар + фото

API=https://api.vizen.shop
TOKEN="vz_pat_XXXX"

# 0) паспорт ключа — контур и валюта ДО первой записи
curl -s "$API/v1/account/token" -H "Authorization: Bearer $TOKEN"

# 1) создать товар
PID=$(curl -s -X POST "$API/products" -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"item":{"name":"Кровать Аврора","price":{"price":45892,"currency":"RUB"},"category_id":12,"seo":{"slug":"avrora"}}}' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["id"])')

# 2) попросить ссылку на загрузку
RESP=$(curl -s -X POST "$API/v1/storages/files" -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"section":"content","file_type":"image","original_name":"avrora.jpg"}')
FILE_ID=$(echo "$RESP" | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["id"])')  # result.id
UP_ID=$(echo "$RESP"  | python3 -c 'import sys,json;print(json.load(sys.stdin)["upload"]["id"])')   # upload.id
UP_URL=$(echo "$RESP" | python3 -c 'import sys,json;print(json.load(sys.stdin)["upload"]["url"])')

# 3) залить байты и подтвердить (в /done — upload.id!)
curl -s -X PUT "$UP_URL" -H "Content-Type: image/jpeg" --data-binary @avrora.jpg
curl -s -X POST "$API/v1/storages/files/$UP_ID/done" -H "Authorization: Bearer $TOKEN"

# 3.1) финальный URL — ПОСЛЕ /done (оригинал контент-секций конвертируется в .webp)
curl -s "$API/v1/storages/files/$FILE_ID" -H "Authorization: Bearer $TOKEN"

# 4) прицепить фото в галерею (preview НЕ трогаем — миниатюра берётся из галереи)
curl -s -X PUT "$API/products/$PID" -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"item\":{\"gallery\":[{\"id\":\"$FILE_ID\"}]}}"

# --- При импорте шаги 2–3.1 схлопываются в один запрос ---
FILE_ID=$(curl -s -X POST "$API/v1/storages/files/from-url" -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source_url":"https://cdn.postavshik.ru/foto/avrora.jpg","section":"content","file_type":"image"}' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["id"])')

# ...а товары — пачкой до 100 штук (разбирайте results по index!)
curl -s -X POST "$API/products/batch" -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"name":"Стол Лофт","price":{"price":18900,"currency":"RUB"},"category_id":12}]}'

5. Лимиты и скорость (для импортёров — обязательно)

  • Размер картинки — до 10 МБ. Превышение отклоняется на шаге /done: 400 FILE_TOO_LARGE, файл в медиатеку не попадает. Сжимайте перед загрузкой (для веба хватает ~1600–2560 px).
  • Rate-limit по компании (все ключи и сотрудники магазина делят один бюджет), окно — минута:

| класс | что входит | лимит/мин | |---|---|---| | чтение | GET / List / Filter / Resolve… | 600 | | запись | создание / правка / удаление | 240 | | хранилище | мутации /v1/storages/* (загрузка файлов) | 180 |

Превышение → HTTP 429 `RATE_LIMITED` с заголовком `Retry-After` (секунды до сброса окна) — уважайте его: ретраи без паузы только продлевают блокировку.

  • Практика импорта: лейте файлы последовательно (загрузка одной картинки presign-путём = 3–4 запроса класса «хранилище» → ~45–60 картинок в минуту в потолке; from-url — один запрос); на 429/503 — пауза по Retry-After и повтор.
  • Товары — квота тарифа products_max; исчерпание → PRODUCT_LIMIT_REACHED.

6. Известные ограничения (честно, чтобы не искать зря)

  • Остаток — число на товаре плюс журнал движений; NULL значит «не учитывается», а не «ноль». Как читать и менять — /docs/stock.
  • Валюта — на уровне магазина, не товара; price.currency не совпала → 400 CURRENCY_IS_STORE_LEVEL (§1.3).
  • `seo` при `PUT` заменяется целиком — §2.
  • Цена рядом со скидкой считается сервером, а не хранится — не пересчитывайте её у себя, читайте /docs/promotions до вывода любой цены.
  • option.value у характеристик — машинный код: только [a-z0-9][a-z0-9_-]* (до 64 символов), иначе 400 INVALID_OPTION_VALUE. Кириллица живёт в label.
  • Опцию, которая уже проставлена товарам, удалить нельзя — PUT /attributes/{id} без неё ответит 400 OPTION_IN_USE:<value> (набор опций только дополняется). Ручки «кто использует значение» пока нет.
  • Настройки витрины, члены компании, ключи и вебхуки ключу закрыты (PAT_METHOD_NOT_ALLOWED) — это делает человек в кабинете.

7. События (вебхуки)

Опрос API — не единственный путь: магазин сам сообщает о происходящем POST-ом на ваш адрес с подписью HMAC-SHA256. Подписку заводит человек в кабинете («API-доступ» → «Вебхуки»), ключом её создать нельзя; события с персональными данными и деньгами (order.*, lead.created, key.issued, цены и акции) подключает только владелец или администратор.

Конверт, заголовки, проверка подписи, гарантии доставки (не менее одного раза, 8 попыток, порядок не гарантирован), полный список событий с полями — `/docs/webhooks`; машинный список событий и параметров доставки — /docs/events.json. Событие — оптимизация, а не источник истины: критичное сверяйте периодическим диффом по updated_at через обычные ручки.

8. Как сдавать работу владельцу

  • Ключ-черновик не публикует. После вашей работы страница живёт в черновой версии сайта: на живом домене будет 404, пока владелец не нажмёт «Опубликовать». Это нормальный результат, а не ошибка.
  • Самопроверка: GET /categories/by-slug/{slug}?company_id={id} с ключом → 200, без ключа → 404. Такая пара = всё сделано верно.
  • Ссылки в отчёте — только полные: https://{slug}.vizen.shop/{страница}; относительный /promo в чате не кликается. Домен магазина — из store.storefront_url паспорта, не выдумывайте. Ссылок на черновой поддомен не давайте — черновик открывается только из кабинета (сессия владельца), по прямой ссылке недоступен намеренно. Рядом со ссылкой пишите «заработает после публикации».
  • Финальный отчёт — четыре пункта: что создано (названиями, не id); куда смотреть (полные URL); что нажать (кабинет → режим ДЕВ → «Опубликовать»); что не вышло — честно. PAT_METHOD_NOT_ALLOWED на настройках витрины (логотип, цвета, валюта, язык) — норма: попросите владельца сделать это в кабинете.
  • По ходу работы: короткие статусы вместо молчания, тексты ошибок API дословно (код вроде SLUG_TAKEN владелец передаст разработчику), данные — только из каталога магазина. Ключ никуда не печатать — ни в ответы, ни в файлы, ни в разметку.

9. Смежные документы

  • Наполнение каталога целиком (категории, характеристики, варианты, склейки, галерея по группам) — /docs/catalog-import.
  • Сверстать страницу магазина кодом (лендинг, промо, квиз) — /docs/webcoding, своя вёрстка на данных магазина — /docs/vz-keys, /docs/own-markup.
  • Скидки и цены — /docs/promotions, /docs/pricing; заказы — /docs/orders.
  • «Ответило 200, а на сайте ничего не изменилось» — /docs/troubleshooting.

*Гайд ведёт бэкенд-команда Vizen; обновляется тем же коммитом, что и контракт. Байт-в-байт копия отдаётся по GET /docs/quickstart; расхождение ловит TestQuickstartGuideInSync.*

Исходник: https://api.vizen.shop/docs/quickstart

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