Перейти к содержанию

Публичный API и интеграции

Помимо встроенного интерфейса, Synth предоставляет публичный API — контракт для внешних сервисов, скриптов и интеграций с ограниченным машинным доступом.

Service-ключи

Service-ключ — это ключ инстанса для внешних сервисов. Он не привязан к конкретному чату (в отличие от ключа привязки событий) и даёт ровно тот набор прав, который вы выдали.

  • Аутентификация: Authorization: Bearer synth_svc_….
  • Секрет показывается один раз при создании; в списке виден только префикс.
  • Метка label обязательна и уникальна по инстансу.
  • Управление: раздел «Сервисные ключи» или API /api/v2/service-keys (в серверном режиме — только ADMIN).

Права ключа

Поле Назначение
roles Роль синтетического пользователя (по умолчанию USER)
scopes Точечные разрешения на операции (см. ниже)
allowedProjects Ограничение доступа указанными проектами
rateLimitPerMinute Лимит запросов в минуту
expiresAt Срок действия ключа

Scopes — это узкие разрешения на группы операций. Примеры:

Scope Разрешает
events:ingest Приём внешних событий
events:watch Чтение потока событий
sessions:create / sessions:read / sessions:prompt Создание сессий, чтение, отправку сообщений
schedules:read / schedules:write Чтение и управление расписаниями
bindings:read / bindings:write Чтение и управление привязками событий
keys:read / keys:write Управление API-ключами привязок
routes:read / routes:write Управление правилами маршрутизации
llm:chat Прямые вызовы LLM
users:read Чтение пользователей и идентичностей

Есть и «семейные» варианты (events:*), но глобального «всё разрешено» нет. Если задан allowedProjects, запрос к чужому проекту отклоняется. Если у ключа не хватает scope или доступа к проекту, сервер отвечает 403.

# Создание ключа (ADMIN). Секрет придёт один раз в поле "key".
curl -X POST http://localhost:5000/api/v2/service-keys \
  -H 'Authorization: Bearer <api-key>' -H 'Content-Type: application/json' \
  -d '{"label":"telegram-gw","roles":["USER"],"scopes":["events:ingest","sessions:create"]}'

Публичный OpenAPI и SDK

Публичный контракт описан декларативно и отдаётся ручкой GET /api/v2/openapi.json (OpenAPI 3.1). В него входит интеграционное ядро — события, сессии, расписания и LLM (28 операций).

Потребить контракт удобнее через типизированный клиент:

npm install @itphx/synth-sdk
import { createSynthClient, SynthApiError } from '@itphx/synth-sdk';

const synth = createSynthClient({
  baseUrl: 'http://localhost:5000', // без /api/v2
  serviceKey: 'synth_svc_…',
});

const { sessionId } = await synth.events.ingest({ source: 'tg', type: 'message' });
const jobs = await synth.schedules.jobs('<schedule-id>');
  • Namespaces: events (ingest/journal/bindings/keys/routes), sessions (CRUD + prompt/messages/status), schedules (CRUD + run/jobs), llm (chatCompletions).
  • Аутентификация: приоритет serviceKey → apiKey → token.
  • Ошибки: SynthApiError ({status, code, message, body?}) с единым форматом ответа об ошибке; сетевой сбой даёт status: 0.

Исходящие webhooks

Webhooks — обратный контур: Synth сам отправляет события вашим подписчикам HTTP-запросом.

  • Подписка задаёт адрес получателя (url, только https), метку и список событий-паттернов (точный тип stream.done или префикс session.*).
  • Запросы к приватным и локальным адресам по умолчанию запрещены (защита от SSRF).
  • Signing-секрет whsec_… выдаётся один раз при создании и ротации.
  • Доставка устойчива к сбоям: повторы с задержкой, дедупликация, журнал доставок.
  • Можно провести тестовую доставку и посмотреть журнал по конкретной подписке.

Проверка подписи

Каждый запрос несёт заголовок X-Synth-Signature вида t=<unix>,v1=<hex>, где v1 — HMAC-SHA256 от строки <t>.<тело запроса> на вашем секрете. Тело берётся сырым, до разбора JSON; расхождение по времени более 5 минут считается недействительным.

const { createHmac, timingSafeEqual } = require('node:crypto');

function verify(rawBody, header, secret, nowSec = Math.floor(Date.now() / 1000)) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=').map((s) => s.trim())));
  const t = Number.parseInt(parts.t, 10);
  if (!Number.isFinite(t) || Math.abs(nowSec - t) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && timingSafeEqual(a, b);
}

Webhooks включаются мастер-выключателем: пока он выключен, создание подписок и тестовая доставка отвечают 503, но список и журнал читаются.

Что дальше