Shop-Script

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

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 для публичных данных магазина. Он покрывает функциональность, которую обычно даёт тема дизайна витрины, но отдаёт её в структурированном виде, удобном для разработки.

wa-magazin.ru
Swigger Shop-Script

Список основных эндпоинтов на сегодня:

  • получение карточки товара и отзывов,
  • подбор сопутствующих и рекомендованных товаров (cross-selling и upselling),
  • работа со списками товаров — по категориям, тегам, наборам,
  • поиск по каталогу,
  • получение списка категорий,
  • расчёт доставки и её стоимости,
  • расчёт и создание заказа,
  • работа с корзиной,
  • получение информации о промо-акциях и о самом магазине,
  • авторизация покупателя через customer token.

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

wa-magazin.ru
1

Headless API уже входит в состав Shop-Script — он указан как штатная часть фреймворка в основном репозитории webasyst/shop-script на GitHub, рядом с REST API, корзиной, заказами и аналитикой.

Как это устроено — простыми словами

Если опустить технические термины, картина такая. У вашего магазина появляется второй вход — не для покупателей и не для админа, а для программ. Через этот вход любая внешняя программа (мобильное приложение, бот, виджет) может «спросить» магазин: «покажи мне товар номер 123», «положи его в корзину», «сколько будет стоить доставка в Самару», «оформи заказ». Магазин отвечает на эти вопросы данными — без картинок, без оформления, только сами факты.

Несколько важных моментов, которые стоит понимать обычному пользователю.

Включается отдельно для каждой витрины. Если у вас в Shop-Script настроено, скажем, две витрины — основной сайт и витрина для оптовых клиентов, — Headless API можно включить только там, где это нужно. Делается это в настройках витрины, в разделе «Headless». Без включения этого пункта внешние программы достучаться до магазина не смогут.

Защита от ботов и злоупотреблений. Запросы к Headless API защищены специальным ключом-антиспамом. Это значит, что нельзя просто так с улицы дёргать ваш магазин миллионами запросов — приложение или виджет должны предъявить правильный ключ. Это защищает от парсинга каталога, накрутки и нагрузки.

wa-magazin.ru
Shop-Script 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

wa-magazin.ru
Shop-Script
JavaScript
<!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>

Читайте также

ИИ в Shop-Script или как нейросеть напишет описания товаров и создаёт баннеры за минуты
Shop-Script7 мая 2026 г.

ИИ в Shop-Script или как нейросеть напишет описания товаров и создаёт баннеры за минуты

В двух прошлых статьях мы разобрали Telegram-магазин, оплату через СБП и кнопку «Забрать в магазине». Сегодня — финальная часть про новинки Shop-Script 12. Поговорим о том, что ещё пару лет назад казалось фантастикой: искусственном интеллекте, встроенном прямо в админку магазина.

Оплата по QR и самовывоз в один клик: как сэкономить на комиссии и ускорить покупку в Shop-Script
Shop-Script7 мая 2026 г.

Оплата по QR и самовывоз в один клик: как сэкономить на комиссии и ускорить покупку в Shop-Script

Сегодня разберём ещё две полезные функции Shop-Script 12

Магазин прямо в мессенджере: как Shop-Script 12 открывает продажи в Telegram и MAX
Shop-Script7 мая 2026 г.

Магазин прямо в мессенджере: как Shop-Script 12 открывает продажи в Telegram и MAX

Если у вас небольшой интернет-магазин на Shop-Script, у меня для вас отличная новость. В новой версии 12 появилась возможность, о которой давно мечтали многие владельцы магазинов: продавать прямо внутри мессенджеров, без сайта и лишних кликов.

Готовы запустить магазин?

Оставьте email и наша команда свяжется с вами для бесплатной консультации