Установка
npm install @sapportly/sdkpnpm add @sapportly/sdkyarn add @sapportly/sdkimport { SapportlyClient } from "@sapportly/sdk";
const client = new SapportlyClient({
// SAPPORTLY_* — канон; SUPPORTLY_* — legacy alias
apiKey: process.env.SAPPORTLY_API_KEY ?? process.env.SUPPORTLY_API_KEY!,
// baseUrl: "https://api.sapportly.pro",
});Карта ресурсов
| Поле | Scope | Задача |
|---|---|---|
ingest | messages:write | Входящие от вашей системы |
conversations | conversations:read|write|assign | История, ответы, назначение |
contacts | conversations:read|write | Email / телефон / внешний id по каналу |
attachments | attachments:write | Файлы через presigned PUT |
realtime | ws:connect | Тикет сокета |
webhooks / rag / channels / team / widget | свои | HMAC-endpoint, база ответов, реестр, роли, embed-session |
Четыре операции
Подробный гайд с copy-paste, протоколом сокета и догоном истории: Входящие и живой канал.
| Намерение | Метод |
|---|---|
| Пользователь → Sapportly | client.ingest.send |
| Узнать, что пользователь написал (если слушаете ленту, а не свой HTTP) | inbox.onVisitor / вебхук message.received |
| Оператор / ИИ / ваш бот → лента | inbox.reply или conversations.reply |
| Узнать, что оператор ответил, и доставить человеку на custom-канале | inbox.onAgent (echo: false) / вебхук agent.reply |
Принять сообщение
Без idempotency_key SDK ставит UUID и дублирует его в заголовок Idempotency-Key — ретрай безопасен. Свой ключ нужен, когда вы дедуплицируете внешнее событие: shop:{order_id}:{comment_id}.
await client.ingest.send({
channel: "custom:shop",
body: "Заказ #1042: не пришёл трек",
identity: { email: "ada@example.com", external_id: "ord_1042" },
});Ответить
await client.conversations.reply(threadChannel, {
body: "Трек 123, доставка завтра.",
});Не приём входящих. Ключ ленты — тред из accepted.channel / msg.channel. Для виджета кадр уйдёт посетителю; для custom — вам с external_id. Диалоги.
Живой канал
SapportlyInbox сам берёт ticket, переподключается и раскладывает кадры. Собственный ответ приходит с echo: true. Черновик ИИ — onAi, это не строка истории. Автоответ ИИ приходит как обычный onAgent.
import { SapportlyInbox } from "@sapportly/sdk/realtime";
const inbox = new SapportlyInbox(client, { channels: ["custom:shop"] });
inbox.onVisitor((m) => console.log("user", m.body));
inbox.onAgent((m) => {
if (m.echo) return;
console.log("agent", m.externalId, m.body);
});
inbox.onAi((d) => {
if (d.partial) return;
console.log("ai", d.draft_body);
});
await inbox.connect();Контакты
const people = await client.contacts.list({ limit: 50 });
await client.contacts.upsert(threadChannel, {
email: "ada@example.com",
external_id: "cus_ada",
});Вложения
const file = await client.attachments.upload({
channel: "custom:shop",
filename: "invoice.pdf",
mimeType: "application/pdf",
content: bytes,
});
await client.ingest.send({
channel: "custom:shop",
body: "",
attachment_ids: [file.id],
});Вебхуки
Подпись: HMAC-SHA256 строки {timestamp}.{rawBody}, заголовки X-Sapportly-Timestamp и X-Sapportly-Signature, окно 300 с. Нужен сырой body. Не используйте @sapportly/api/webhooks — там подпись только по телу.
import { verifyWebhook } from "@sapportly/sdk/webhooks";
await verifyWebhook(secret, rawBody, {
timestamp: req.header("X-Sapportly-Timestamp"),
signature: req.header("X-Sapportly-Signature"),
});База знаний
await client.rag.upload({
title: "Доставка",
body: "Сроки по регионам…",
});Ошибки
Наследники SapportlyError. В теле API: type, code, message, request_id, docs_url. SDK прокидывает их в error.code, error.docsUrl, error.requestId. Ретраи на 429/5xx с jitter; небезопасные POST без идемпотентности не повторяются.
import { SapportlyRateLimitError, SapportlyPermissionError } from "@sapportly/sdk";
try {
await client.ingest.send({ channel: "custom:shop", body: "Hi" });
} catch (error) {
if (error instanceof SapportlyRateLimitError) console.warn(error.retryAfterMs);
if (error instanceof SapportlyPermissionError) console.error(error.docsUrl);
}Пагинация
SDK всегда запрашивает конверт списка и отдаёт массив. Итераторы сами ходят за следующей страницей.
for await (const c of client.conversations.iterate({ limit: 100 })) {
console.log(c.channel, c.last_message, c.last_at);
}