Покроковий приклад: ваш API від ендпоінта до екрана
Для розробників, у коді: як зробити ендпоінт, який викликає помічник, описати його в панелі як інструмент і подію та намалювати те, що він повертає, на власній сторінці — сітка тарифів і каталог магазину на нашому сайті, крок за кроком, з рішеннями, завдяки яким це працює.
Дві демонстрації на цьому сайті працюють саме на тому, що описує ця сторінка. Спитайте помічника «який тариф найдешевший для трьох сайтів?» — і поруч із відповіддю відкриється вікно з тарифами, що підходять. На сторінці для магазинів попросіть «червону сукню на весілля, до 2 000» — і сукні з’являться у власній вітрині сторінки. Жодна з них не є особливим кодом: кожна — це один ендпоінт, один інструмент, одна подія й один слухач. Ось вони в тому порядку, в якому ви збиратимете свої.
Власні інструменти й події входять у тариф Growth і вищі.
1. Ендпоінт
Помічник викликає звичайний HTTP-ендпоінт, що відповідає JSON. Наш для тарифів —
GET /api/public/plans/ у нашій панелі; він відповідає:
{
"currency": "USD",
"plans": [
{
"code": "growth",
"name": "Growth",
"price_usd": "29.00",
"first_month_price_usd": null,
"credits": 7000,
"sites": 3,
"pages_per_site": 2000,
"history_days": 90,
"advanced_tools": true
}
]
}
П’ять рішень роблять ендпоінт зручним для помічника.
Необов’язкові фільтри, які модель заповнює з питання. Кожен параметр сітки тарифів необов’язковий, і вони поєднуються:
| Відвідувач питає | Помічник викликає |
|---|---|
| «Які є тарифи?» | /api/public/plans/ |
| «Який найдешевший?» | ?cheapest=true |
| «Найдешевший для трьох сайтів» | ?min_sites=3&cheapest=true |
| «Щось до $50» | ?max_price=50 |
| «У мене магазин, хочу свій API» | ?tools=true |
| «Розкажи про Pro» | ?plan=pro |
Значення, яке ваш ендпоінт не може прочитати, краще проігнорувати, ніж відхилити: max_price=cheap
повертає всю сітку, а не 400, бо викликає модель, що заповнює URL, і повна відповідь корисніша
для неї за помилку.
Фільтри з переліченими значеннями для великого каталогу. Ендпоінт магазину приймає colour,
size, occasion, maker, max_price і вільний query — усі необов’язкові, але виклик без
жодного з них відхиляється, тож кожен виклик — це пошук, і каталог на тисячі позицій ніколи не
надсилається цілим. Опис інструмента перелічує значення кожного фільтра й те, як на них лягають
слова відвідувача: «світлі» — це ivory і pink, «великі» — L і XL, «на вечірку» —
evening і cocktail. Модель заповнює фільтри з цих переліків будь-якою мовою, а значення, якого
немає в переліку, — це те, чого в магазині немає. Кілька значень одного фільтра означають «будь-яке
з них».
Кажіть, наскільки близький кожен результат. Кожен товар несе fits (підходить під усе, що
просили) і differs (фільтри, під які не підходить). Коли не підходить нічого, ендпоінт повертає
найближчі сукні — ті, що не збігаються з найменшою кількістю фільтрів, і розмір важить найбільше,
— а помічник каже, що точного збігу немає, і називає, чим кожна відрізняється. Коли нічого навіть
близького немає, список порожній.
Результати одним списком під одним ключем. plans, products: Шлях у відповіді
інструмента обрізає відповідь до цього списку, і саме цей список подія несе на вашу сторінку.
Назви полів і одиниці, що пояснюють себе. price_usd — долари США на місяць,
pages_per_site рахує сторінки по всьому акаунту, price товару — цілі гривні. Скажіть те саме
в описі відповіді інструмента (крок 2): модель читає цей опис раніше за самі дані.
Cache-Control: max-age, коли дані змінюються повільно. Сітка тарифів відповідає
Cache-Control: public, max-age=300, тож п’ять хвилин помічник використовує цю відповідь
повторно, а не викликає знову. Баланс чи статус замовлення такого заголовка не надсилають і
викликаються щоразу. Кешування відповідей.
Невелика версія сітки тарифів на Node з Express:
app.get("/plans", (req, res) => {
const { cheapest, max_price, min_sites } = req.query;
let plans = PLANS;
const budget = Number(max_price);
if (Number.isFinite(budget)) plans = plans.filter((p) => p.price_usd <= budget);
const sites = Number(min_sites);
if (Number.isFinite(sites)) plans = plans.filter((p) => p.sites >= sites);
if (cheapest === "true" && plans.length) {
const lowest = Math.min(...plans.map((p) => p.price_usd));
plans = plans.filter((p) => p.price_usd === lowest);
}
res.set("Cache-Control", "public, max-age=300").json({ currency: "USD", plans });
});
Адреса має бути https на публічному хості; перенаправлення не виконуються.
Коли ендпоінту потрібен токен
Сітка тарифів публічна навмисно — ті самі ціни є на нашій сторінці тарифів. Непублічний ендпоінт
бере токен із секрету: збережіть значення в Секретах на формі інструмента й запишіть
заголовок Authorization: Bearer {{secret.api_token}}. Значення зашифроване, більше ніколи не
показується і не видне моделі. Тримайте токени поза URL, який ви вписуєте у форму; заголовок —
саме те місце, де їх чекають журнали вашого API. Секрети.
Коли дані належать відвідувачу
«Де моє замовлення?» потребує власних даних відвідувача, що увійшов. Ваша сторінка передає
віджету токен цього відвідувача через Shukai.setUser(token) після входу, а інструмент надсилає
його як Authorization: Bearer {{user}}. Тоді ваш ендпоінт відповідає за цю людину так само, як
для вашого власного застосунку. 401 чи 403 помічнику повідомляються як «схоже, відвідувач вийшов
з акаунта», і він так і каже. Ідентичність користувача.
2. Інструмент
У панелі: Інструменти → Новий інструмент. Сітка тарифів так, як ми її налаштували:
| Поле | Значення |
|---|---|
| Назва для показу | Plan grid |
| Ім’я | subscription_search |
| Коли використовувати | нижче |
| Метод · URL | GET · https://api.example.com/plans |
| Параметри | plan, cheapest, max_price, min_sites, min_pages, min_credits, tools — усі необов’язкові, у query |
| Що означає відповідь | нижче |
| Шлях у відповіді | plans |
| Емітує подію | subscription_lookup (крок 3) |
Коли використовувати — єдине, що модель читає, вирішуючи, чи викликати. Пишіть його як ситуації, словами відвідувача, і кажіть, що кожна означає для параметрів. Писати можна будь-якою мовою; наш опис — англійською:
The live plan grid — the source of truth for plans and prices. Call it for any
question about plans, prices, credits or which plan suits the visitor; never
answer prices from memory. The page shows the visitor exactly the plans this call
returns, so filter to what they asked: «what plans are there?» → no arguments;
«the cheapest» → cheapest=true; «for three sites» → min_sites=3; «under $50» →
max_price=50; «a shop», «my own API» → tools=true.
Більшість роботи в ньому роблять два речення: «never answer prices from memory» спрямовує кожне питання про ціну через ваші дані, а «the page shows exactly what this call returns» — причина, чому модель звужує виклик, а не бере все.
Кожен параметр отримує тип і один рядок для моделі: cheapest — boolean, «true: з тарифів,
що підходять, лише найнижча ціна»; min_sites — integer, «скільки сайтів потрібно відвідувачу».
size магазину — array, «XS, S, M, L, XL — «маленькі» це XS і S, «великі» — L і XL»; його
query — string, «усе речення відвідувача, для того, що фільтри не охоплюють».
Що означає відповідь називає кожне поле, яке побачить модель, з його одиницею:
Returns {currency, plans}; each plan has code, name, price_usd (US dollars a month,
as a string), first_month_price_usd (the first month's price, or null when there is
no offer), credits (a month), sites, pages_across_sites (pages across all the
account's sites together), history_days (how long conversations are kept) and advanced_tools
(true when the plan includes your own API tools, events and page actions).
Поле, якого опис не називає, модель мусить вгадувати; назване — вона цитує правильно.
Мова. Сітка тарифів не має мови, тож Мови, які підтримує API лишаються порожніми, і
параметр мови не надсилається. Каталог магазину відповідає англійською й українською: він
перелічує обидві й називає lang своїм Параметром мови — рантайм заповнює його мовою
відвідувача, і модель ніколи її не обирає. Обидва поля — у Додатково, поруч із Кешувати
відповіді, що для таких живих даних лишається вимкненим.
Тоді Зберегти, і в блоці Тест натисніть Запустити з cheapest=true: панель зробить
справжній виклик і покаже сиру відповідь. Саме час перевірити шлях у відповіді й назви полів.
3. Подія
Інструмент приносить дані помічнику. Подія приносить їх на вашу сторінку. Інструменти → Нова подія:
| Поле | Тарифи | Магазин |
|---|---|---|
| Ім’я | subscription_lookup |
show_products |
| Поля payload | plans, array, обов’язкове |
products, array, обов’язкове |
| Не пропонувати перехід на сторінку | вимкнено | увімкнено |
| Відповідати коротко замість повної відповіді | вимкнено | вимкнено |
Тоді на інструменті поставте Емітує подію на неї. Прив’язану подію надсилає сам рантайм на
кожен непорожній результат, з усім списком у її полі array — модель цього не вирішує, тож
сторінка щоразу малює кожен результат. Власні події.
Два перемикачі — це вибір того, де відповідь:
- Відповідати коротко замість повної відповіді — коли вся відповідь — це власний інтерфейс вашої сторінки: помічник каже «знайшов чотири — ось вони» і зупиняється, бо письмовий перелік того, що вже на екрані, лише повторив би його.
- Лишіть вимкненим, коли відповідь має сказати те, чого сторінка не скаже. На «Який найдешевший?» відповідають словами, а вікно тарифів поруч показує сітку. Магазин теж лишає його вимкненим: коли точно не підходить жодна сукня, відповідь має це сказати й назвати, чим відрізняються найближчі, а коротка відповідь назвала б їх збігом.
- Не пропонувати перехід на сторінку — коли результат на екрані замінює сторінку, яку помічник інакше запропонував би. Увімкнено для магазину: сукні вже тут, посилання на сторінку каталогу нічого не додає. Вимкнено для тарифів: у вікні є власне посилання на сторінку тарифів, і «відкрий тарифи» досі веде туди.
4. Слухач
Один слухач на вашій сторінці, на кожній сторінці, де подія може спрацювати. Наш стоїть у шаблоні сайту:
window.addEventListener("shukai:action:subscription_lookup", (event) => {
const plans = event.detail.payload.plans;
if (!Array.isArray(plans) || plans.length === 0) return;
showPlansWindow(plans);
});
event.detail — це { type, payload }, а payload несе оголошені вами поля. Три звички нашого
власного слухача:
- Малюйте власними компонентами, у стилі вашої сторінки — вікно на цьому сайті має стиль картки й слова самої сторінки тарифів.
- Ставтеся до payload як до даних. Наше вікно екранує кожне значення, перш ніж воно потрапить у розмітку, і саме будує свої посилання: посилання на сторінку тарифів бере сайт, а не payload.
- Закривайте за Escape і кліком поза вікном і лишайте віджет видимим: відвідувач розмовляє далі, поки дивиться.
5. Тарифи й ліміти
На Growth і вищих на сайті може бути 6, 8 або 11 інструментів, доступних на кожному екрані (Growth, Pro, Business), і значно більше прив’язаних до окремих екранів. В одній відповіді виконується до 3 викликів інструментів, кожен у межах тайм-ауту щонайбільше 10 секунд. Повна таблиця — у Власних інструментах.
Перелік
- Один ендпоінт, https, JSON, список під одним ключем.
- Необов’язкові фільтри; для великого каталогу — перелічені значення, на які модель кладе слова, і хоча б один фільтр у кожному виклику.
- Коли використовувати словами відвідувача, зіставленими з параметрами.
- Що означає відповідь, де названо кожне поле й одиницю.
- Шлях у відповіді до списку; Емітує подію на подію з одним полем
array. - Відповідати коротко, коли відповідь показує сторінка; Не пропонувати перехід на сторінку, коли результат замінює сторінку.
Cache-Control: max-ageдля даних, що змінюються повільно.- Токени як
{{secret.*}}у заголовку; власні дані відвідувача — через{{user}}. - Зберегти, тоді Тест → Запустити з питаннями, яких ви чекаєте.