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: true(вitem) — товар появляется в магазине сразу (в прод-контуре; в дев-контуре — после публикации черновика человеком).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 файла, который цепляется к товару.
- Попросить ссылку на загрузку —
POST /v1/storages/files(scopestorage: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. Их путают чаще всего.
- Залить байты напрямую —
PUT <upload.url>, тело = сырые байты файла, заголовокContent-Type: image/jpeg(должен совпадать с типом файла). Это прямой PUT в хранилище безAuthorization— ссылка сама авторизует.
- Подтвердить —
POST /v1/storages/files/<UPLOAD_ID>/done(scopestorage:write). Здесь используетсяupload.id, а неresult.id.
⚠️ PNG/JPEG-оригиналы контента конвертируются в WebP. Для контент-секций (content,products) сервер на шаге/doneконвертирует растровый оригинал в WebP q90 и не хранит исходный PNG/JPEG.idфайла стабилен, но URL меняется (расширение станет.webp) —public_pathиз ответа шага 1 после/doneможет устареть. Правило: финальный URL читайте после/done—GET /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_path→https://<магазин>.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