К содержанию
sapportlydocs
Панель

Sapportly TypeScript SDK

@sapportly/sdk — официальный клиент публичного API Sapportly. Не @supportly/sdk: это имя на npm недоступно. Node 18+, Bun, Deno, Workers, браузеры. Без runtime-зависимостей.

Установка

npm install @sapportly/sdk
client.tsTypeScript
import { 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Задача
ingestmessages:writeВходящие от вашей системы
conversationsconversations:read|write|assignИстория, ответы, назначение
contactsconversations:read|writeEmail / телефон / внешний id по каналу
attachmentsattachments:writeФайлы через presigned PUT
realtimews:connectТикет сокета
webhooks / rag / channels / team / widgetсвоиHMAC-endpoint, база ответов, реестр, роли, embed-session

Четыре операции

Подробный гайд с copy-paste, протоколом сокета и догоном истории: Входящие и живой канал.

НамерениеМетод
Пользователь → Sapportlyclient.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}.

TypeScript
await client.ingest.send({
  channel: "custom:shop",
  body: "Заказ #1042: не пришёл трек",
  identity: { email: "ada@example.com", external_id: "ord_1042" },
});

Ответить

TypeScript
await client.conversations.reply(threadChannel, {
  body: "Трек 123, доставка завтра.",
});

Не приём входящих. Ключ ленты — тред из accepted.channel / msg.channel. Для виджета кадр уйдёт посетителю; для custom — вам с external_id. Диалоги.

Живой канал

SapportlyInbox сам берёт ticket, переподключается и раскладывает кадры. Собственный ответ приходит с echo: true. Черновик ИИ — onAi, это не строка истории. Автоответ ИИ приходит как обычный onAgent.

@sapportly/sdk/realtimeTypeScript
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();

Контакты

TypeScript
const people = await client.contacts.list({ limit: 50 });
await client.contacts.upsert(threadChannel, {
  email: "ada@example.com",
  external_id: "cus_ada",
});

Вложения

TypeScript
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 — там подпись только по телу.

TypeScript
import { verifyWebhook } from "@sapportly/sdk/webhooks";

await verifyWebhook(secret, rawBody, {
  timestamp: req.header("X-Sapportly-Timestamp"),
  signature: req.header("X-Sapportly-Signature"),
});

База знаний

TypeScript
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 без идемпотентности не повторяются.

TypeScript
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 всегда запрашивает конверт списка и отдаёт массив. Итераторы сами ходят за следующей страницей.

TypeScript
for await (const c of client.conversations.iterate({ limit: 100 })) {
  console.log(c.channel, c.last_message, c.last_at);
}

Дальше

Страница полезна?

/docs/sdk/typescript
Sapportly TypeScript SDK — @sapportly/sdk · Sapportly Docs