Shop-Script Headless API: магазин без витрины — и почему это плюс

Долгое время интернет-магазин и его внешний вид были одним целым. Сайт, темы оформления, корзина, страницы товаров — всё это жило в одной системе, и любая попытка вынести продажи за пределы сайта упиралась в архитектуру. Shop-Script меняет правила: с появлением Headless API магазин превращается в источник данных, а витрин у него может быть сколько угодно — и каких угодно.
Что такое headless и зачем он нужен
Идея headless-подхода простая: бэкенд и фронтенд разводятся по разным углам. Сервер хранит товары, считает корзину, оформляет заказы, а как и где это всё будет показано покупателю — отдельный вопрос. Связь между двумя половинами идёт через API: фронтенд спрашивает — бэкенд отвечает структурированными данными в формате JSON.
У такого разделения нет «лица по умолчанию». Headless API — это набор методов, а не готовая витрина. Получить список товаров, добавить позицию в корзину, посчитать доставку, создать заказ — всё это есть, но как именно покупатель увидит карточку товара, решает разработчик каждой конкретной витрины.
Звучит как ограничение, а на деле — свобода. В монолитной архитектуре магазин намертво прибит к сайту. В headless-подходе тот же магазин может одновременно продавать через сайт, мобильное приложение, чат-бот и встроенный виджет на чужом лендинге — и все заказы будут падать в одну общую систему.
Что именно умеет Shop-Script Headless API
Headless API в Shop-Script — открытый JSON API для публичных данных магазина. Он покрывает функциональность, которую обычно даёт тема дизайна витрины, но отдаёт её в структурированном виде, удобном для разработки.

Список основных эндпоинтов на сегодня:
- получение карточки товара и отзывов,
- подбор сопутствующих и рекомендованных товаров (cross-selling и upselling),
- работа со списками товаров — по категориям, тегам, наборам,
- поиск по каталогу,
- получение списка категорий,
- расчёт доставки и её стоимости,
- расчёт и создание заказа,
- работа с корзиной,
- получение информации о промо-акциях и о самом магазине,
- авторизация покупателя через customer token.
Подключается API на уровне витрины (поселения) в разделе Витрина → Headless. Каждая витрина получает свою OpenAPI-спецификацию — её можно открыть в Swagger и сразу видеть все доступные методы с параметрами и примерами ответов. Для разработчика это значит ноль времени на разбирательство с документацией: спека генерируется под конкретный магазин.

Headless API уже входит в состав Shop-Script — он указан как штатная часть фреймворка в основном репозитории webasyst/shop-script на GitHub, рядом с REST API, корзиной, заказами и аналитикой.
Как это устроено — простыми словами
Если опустить технические термины, картина такая. У вашего магазина появляется второй вход — не для покупателей и не для админа, а для программ. Через этот вход любая внешняя программа (мобильное приложение, бот, виджет) может «спросить» магазин: «покажи мне товар номер 123», «положи его в корзину», «сколько будет стоить доставка в Самару», «оформи заказ». Магазин отвечает на эти вопросы данными — без картинок, без оформления, только сами факты.
Несколько важных моментов, которые стоит понимать обычному пользователю.
Включается отдельно для каждой витрины. Если у вас в Shop-Script настроено, скажем, две витрины — основной сайт и витрина для оптовых клиентов, — Headless API можно включить только там, где это нужно. Делается это в настройках витрины, в разделе «Headless». Без включения этого пункта внешние программы достучаться до магазина не смогут.
Защита от ботов и злоупотреблений. Запросы к Headless API защищены специальным ключом-антиспамом. Это значит, что нельзя просто так с улицы дёргать ваш магазин миллионами запросов — приложение или виджет должны предъявить правильный ключ. Это защищает от парсинга каталога, накрутки и нагрузки.

Своя документация для каждого магазина. Webasyst сгенерировал не один общий мануал на всех, а отдельную спецификацию для каждой витрины — в формате OpenAPI (его же ещё называют Swagger). Это удобно для разработчика: он открывает спеку именно вашего магазина и видит, какие там доступны категории, какие параметры товаров, как именно работает корзина в вашем случае. Никаких «у нас в общем-то так, но в вашем случае может быть иначе».
Заказы попадают в общий поток. Это, пожалуй, самое важное для бизнеса. Что бы вы ни прикрутили к магазину через Headless API — мобильное приложение, бот, виджет на чужом сайте — все заказы прилетают в Shop-Script в один общий список. Менеджеры обрабатывают их так же, как заказы с сайта: те же статусы, те же уведомления, та же история клиента. Снаружи всё разное, внутри — один магазин.
Никакого внешнего вида. Это стоит подчеркнуть, потому что часто вызывает путаницу. Headless API ничего не рисует. Он не приносит с собой ни кнопок, ни шрифтов, ни цветов. Внешний вид мобильного приложения, бота или виджета каждый раз делается отдельно — разработчиком или с помощью готовых решений (как мини-приложение для Telegram, которое уже готовит Webasyst). API только отдаёт данные и принимает команды.
Где это реально пригодится
Headless API открывает несколько направлений — разберём их по очереди с примерами, как это может выглядеть на практике.
Мобильное приложение-витрина
Самое очевидное применение. У магазина появляется собственное приложение для iOS и Android, в котором покупатель листает каталог, кладёт товары в корзину и оплачивает заказ — не открывая браузер. Все данные приложение получает через Headless API, а заказы уходят в ту же систему, что и с сайта.
Пример: магазин спортивного питания с постоянной аудиторией. Лояльные клиенты ходят за добавками каждый месяц, и каждый раз открывать сайт, логиниться, искать привычную позицию — лишние шаги. Приложение со списком «купить снова», push-уведомлениями о новых вкусах и быстрым оформлением в один тап превращает рутинную покупку в три касания.
Мини-приложения для мессенджеров
Webasyst уже делает мини-приложения, которые встраивают витрину магазина прямо в мессенджер. Подписчики бота заходят в приложение, выбирают товары, оформляют заказ — и заказ падает в общий поток Shop-Script так же, как если бы пришёл с сайта. Сейчас уже доступны мини-приложения для Telegram и Max, следом запланировано приложение для ВК.
Пример: локальная пекарня ведёт канал с фотографиями свежих круассанов по утрам. Раньше подписчик, увидев утренний пост, должен был перейти на сайт, найти ту самую булку, оформить доставку. Теперь он жмёт кнопку прямо в канале, открывает мини-приложение, добавляет круассаны в корзину и выбирает «забрать через 40 минут». Конверсия из подписчика в покупателя растёт, потому что путь короче.
Виджет витрины для встраивания
JS-виджет, который можно вставить на любой сайт или лендинг. Каталог товаров, корзина, оформление заказа — всё работает внутри чужой страницы, но данные и заказы остаются в Shop-Script.
Пример: блог о кофе с обзорами зёрен. Автор не хочет содержать полноценный магазин, но хочет, чтобы читатели могли купить зёрна, о которых он только что написал. Виджет под обзором показывает упомянутые позиции с кнопкой «купить» — и читатель оформляет заказ, не выходя из материала.
Чат-бот с оформлением заказов
Headless API позволяет собрать бота, который рекомендует товары и принимает заказы прямо в диалоге.
Пример: магазин косметики с консультантом-ботом. Покупатель пишет «нужен крем для сухой кожи зимой», бот через API ищет подходящие позиции, показывает три варианта с разными ценовыми категориями, и тут же оформляет заказ выбранного. Никаких переходов на сайт.
Нестандартные точки продаж
Раз API открытый, на нём можно построить что угодно: терминал самообслуживания в офлайн-магазине, кассу с тем же каталогом, что и онлайн, заказ через умную колонку, интерфейс на умных часах. Технически это всё — клиенты к одному и тому же API.
Что это даёт бизнесу
Главный смысл — новые каналы продаж в дополнение к основному сайту. Магазин перестаёт зависеть от того, дойдёт ли покупатель до сайта: его можно встретить там, где он уже есть, — в мессенджере, в приложении, в чужом блоге.
Технология открытая, применять её можно гибко. От микросервиса, который синхронизирует цены с маркетплейсом, до сложной схемы приёма заказов через несколько каналов одновременно — всё это собирается из тех же эндпоинтов.
Что доступно прямо сейчас
Headless API уже в составе Shop-Script — его не нужно ждать или ставить отдельно из ветки разработки. На его базе Webasyst уже выпустил мини-приложения для Telegram и Max, на очереди — мини-приложение для ВК и JS-виджет витрины для встраивания на любые сайты.
Если у вас есть свой Shop-Script и идея, какой канал продаж хотелось бы открыть, — Headless API скорее всего уже умеет всё необходимое, чтобы её реализовать.
Пример мини-приложения для Shop-Script Headless API

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мини-витрина на Shop-Script Headless API</title>
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 24px;
background: #fafafa;
color: #222;
}
h1 { margin: 0 0 8px; }
.subtitle { color: #888; margin-bottom: 24px; }
.layout { display: grid; grid-template-columns: 1fr 320px; gap: 24px; }
@media (max-width: 700px) { .layout { grid-template-columns: 1fr; } }
.products { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px; }
.card {
background: white;
border-radius: 12px;
padding: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
display: flex;
flex-direction: column;
}
.card-img {
width: 100%;
height: 120px;
background: #f0f0f0;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
margin-bottom: 12px;
}
.card-name { font-weight: 600; margin-bottom: 4px; }
.card-price { color: #0a7; font-weight: 700; margin-bottom: 12px; }
.btn {
background: #2563eb;
color: white;
border: none;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
margin-top: auto;
}
.btn:hover { background: #1d4ed8; }
.btn:disabled { background: #ccc; cursor: not-allowed; }
.cart {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
height: fit-content;
position: sticky;
top: 20px;
}
.cart h2 { margin: 0 0 16px; font-size: 18px; }
.cart-empty { color: #888; font-size: 14px; }
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eee;
font-size: 14px;
}
.cart-item-name { flex: 1; }
.cart-item-qty { color: #888; margin: 0 8px; }
.cart-remove {
background: none; border: none; color: #c33;
cursor: pointer; font-size: 16px; padding: 0 4px;
}
.cart-total {
display: flex; justify-content: space-between;
font-weight: 700; margin-top: 12px; padding-top: 12px;
border-top: 2px solid #222;
}
.checkout-btn {
width: 100%; margin-top: 16px;
background: #0a7; padding: 12px;
font-size: 15px; font-weight: 600;
}
.checkout-btn:hover { background: #096; }
.log {
background: #1a1a1a;
color: #0f0;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 12px;
padding: 16px;
border-radius: 8px;
margin-top: 24px;
max-height: 200px;
overflow-y: auto;
}
.log-title { color: #888; margin-bottom: 8px; }
.log-line { white-space: pre-wrap; line-height: 1.5; }
.log-line.req { color: #6cf; }
.log-line.res { color: #ffa; }
.notice {
background: #fff7e0;
border-left: 4px solid #fb0;
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 24px;
font-size: 14px;
}
</style>
</head>
<body>
<h1>🛍 Мини-витрина пекарни</h1>
<p class="subtitle">Простой пример витрины на чистом JavaScript через Headless API</p>
<div class="notice">
<strong>Демо-режим:</strong> запросы идут в локальную заглушку (имитация Shop-Script Headless API).
В реальном проекте надо заменить <code>API_BASE</code> на адрес вашего магазина и добавить антиспам-ключ.
</div>
<div class="layout">
<div>
<div class="products" id="products">Загрузка товаров…</div>
</div>
<div class="cart" id="cart">
<h2>Корзина</h2>
<div id="cart-content" class="cart-empty">Пусто</div>
</div>
</div>
<div class="log">
<div class="log-title">Лог запросов к API:</div>
<div id="log"></div>
</div>
<script>
// ============================================================
// КОНФИГ
// ============================================================
// Для боевого подключения:
// const API_BASE = 'https://your-shop.com/api.php/headless';
// const API_KEY = 'ваш-антиспам-ключ';
// Сейчас работаем через заглушку (см. mockFetch ниже).
const API_BASE = '/headless'; // условный путь
const API_KEY = 'demo-key';
// ============================================================
// СОСТОЯНИЕ КОРЗИНЫ
// ============================================================
const cart = []; // [{ id, name, price, qty }]
// ============================================================
// ОБЁРТКА НАД FETCH — единое место для запросов к API
// ============================================================
async function api(endpoint, options = {}) {
const url = API_BASE + endpoint;
const opts = {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
'X-Antispam-Key': API_KEY,
},
...(options.body ? { body: JSON.stringify(options.body) } : {})
};
log('req', `→ ${opts.method} ${endpoint}` + (options.body ? '\n ' + JSON.stringify(options.body) : ''));
// В реальном проекте: const res = await fetch(url, opts);
const res = await mockFetch(endpoint, opts);
const data = await res.json();
log('res', `← ${JSON.stringify(data).slice(0, 120)}${JSON.stringify(data).length > 120 ? '…' : ''}`);
return data;
}
// ============================================================
// ЗАГРУЗКА ТОВАРОВ
// ============================================================
async function loadProducts() {
const data = await api('/products/search/?limit=8');
renderProducts(data.products);
}
function renderProducts(products) {
const root = document.getElementById('products');
root.innerHTML = '';
products.forEach(p => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="card-img">${p.emoji}</div>
<div class="card-name">${p.name}</div>
<div class="card-price">${p.price} ₽</div>
<button class="btn">В корзину</button>
`;
card.querySelector('button').onclick = () => addToCart(p);
root.appendChild(card);
});
}
// ============================================================
// КОРЗИНА
// ============================================================
async function addToCart(product) {
// В реальном API — POST /cart/ с id товара
await api('/cart/', { method: 'POST', body: { product_id: product.id, quantity: 1 } });
const existing = cart.find(i => i.id === product.id);
if (existing) existing.qty++;
else cart.push({ ...product, qty: 1 });
renderCart();
}
function removeFromCart(id) {
const idx = cart.findIndex(i => i.id === id);
if (idx > -1) cart.splice(idx, 1);
renderCart();
}
function renderCart() {
const root = document.getElementById('cart-content');
if (cart.length === 0) {
root.className = 'cart-empty';
root.innerHTML = 'Пусто';
return;
}
root.className = '';
const total = cart.reduce((sum, i) => sum + i.price * i.qty, 0);
root.innerHTML = cart.map(i => `
<div class="cart-item">
<span class="cart-item-name">${i.name}</span>
<span class="cart-item-qty">×${i.qty}</span>
<span>${i.price * i.qty} ₽</span>
<button class="cart-remove" data-id="${i.id}">×</button>
</div>
`).join('') + `
<div class="cart-total">
<span>Итого:</span>
<span>${total} ₽</span>
</div>
<button class="btn checkout-btn">Оформить заказ</button>
`;
root.querySelectorAll('.cart-remove').forEach(b =>
b.onclick = () => removeFromCart(Number(b.dataset.id))
);
root.querySelector('.checkout-btn').onclick = checkout;
}
// ============================================================
// ОФОРМЛЕНИЕ ЗАКАЗА
// ============================================================
async function checkout() {
const order = {
items: cart.map(i => ({ product_id: i.id, quantity: i.qty })),
customer: { name: 'Иван Покупатель', phone: '+7 900 000 00 00' },
shipping: { type: 'pickup', time: '40 мин' }
};
const result = await api('/order/create/', { method: 'POST', body: order });
alert(`✅ Заказ № ${result.order_id} оформлен!\nЗабрать через ${order.shipping.time}.`);
cart.length = 0;
renderCart();
}
// ============================================================
// ЛОГИРОВАНИЕ
// ============================================================
function log(type, text) {
const root = document.getElementById('log');
const line = document.createElement('div');
line.className = 'log-line ' + type;
line.textContent = text;
root.appendChild(line);
root.scrollTop = root.scrollHeight;
}
// ============================================================
// ЗАГЛУШКА API (имитация ответов Shop-Script Headless)
// В реальной интеграции этот блок не нужен — fetch ходит на ваш магазин.
// ============================================================
async function mockFetch(endpoint, opts) {
await new Promise(r => setTimeout(r, 200)); // имитация задержки сети
if (endpoint.startsWith('/products/search/')) {
return jsonResponse({
products: [
{ id: 1, name: 'Круассан классический', price: 120, emoji: '🥐' },
{ id: 2, name: 'Багет', price: 90, emoji: '🥖' },
{ id: 3, name: 'Чиабатта', price: 110, emoji: '🍞' },
{ id: 4, name: 'Маффин', price: 150, emoji: '🧁' },
{ id: 5, name: 'Печенье овсяное', price: 60, emoji: '🍪' },
{ id: 6, name: 'Пончик', price: 80, emoji: '🍩' },
],
total: 6
});
}
if (endpoint === '/cart/' && opts.method === 'POST') {
return jsonResponse({ status: 'ok', cart_total_items: 1 });
}
if (endpoint === '/order/create/') {
return jsonResponse({ order_id: Math.floor(Math.random() * 10000) + 1000, status: 'new' });
}
return jsonResponse({ error: 'not_found' }, 404);
}
function jsonResponse(data, status = 200) {
return { json: async () => data, status };
}
// ============================================================
// СТАРТ
// ============================================================
loadProducts();
</script>
</body>
</html>Готовы запустить магазин?
Оставьте email и наша команда свяжется с вами для бесплатной консультации


