Попробовать API
</>
API как платформа: multi-tenant, white-label и open SDK
platformaРазбор
15 мин

API как платформа: multi-tenant, white-label и open SDK

Один эндпоинт `/contacts` — ещё не платформа. Платформа начинается там, где на одном API работают сотня клиентов агентства, партнёр встраивает API в свой продукт, а security-команда корпоративного клиента читает исходник SDK без NDA. TL;DR: три архитектурных свойства — multi-tenant с изоляцией `project_id`, white-label-слой и open-source SDK — превращают API из «ручки для дёргания» в платформу. Разбираем каждое на реальном Public API BOTIX (`v1.1.0`), с кодом и без выдумки.

Коротко

  • Multi-tenant — не настройка, а архитектура: один SDK на 100 ботов агентства держится на изоляции project_id (WHERE project_id = ? + Auth::assertProjectAccess()), одном инстансе SDK на проект, суженных scope и ротации ключей без даунтайма.
  • White-label — набор свойств, закладываемых заранее: отдельный тип аутентификации, sub-проекты, жёсткая изоляция, usage через API. Сегодня в BOTIX это OAuth-installations (btx_oauth_*); полноценный partner-API с sub-проектами — в roadmap, честно помечаем как «ещё не в проде».
  • Open-source SDK — не подарок сообществу, а инструмент: security-аудит без NDA, bug fix через PR, сигнал зрелости. Три zero-dependency SDK BOTIX (botix-pro/sdk, botix, @botix/sdk) выложены на GitHub.

В большинстве SaaS-команд «стать платформой» обсуждают как маркетинговую амбицию. На деле это три конкретных технических свойства, каждое из которых либо заложено в API с первого дня, либо дорого достраивается после первых жалоб интеграторов. Combo этой статьи — три тезиса, по одному на свойство: multi-tenant, white-label, open-source SDK. Смежные грани — в разборах API как платформа: OAuth, scope, изоляция project_id и OAuth 2.0 vs API-ключ: когда что в SaaS-интеграциях.

Multi-tenant: один SDK на 100 ботов агентства без утечек между проектами

Сценарий. Агентство строит чат-ботов на платформе и ведёт сотню клиентов. Каждый клиент — отдельный проект на стороне платформы: своя база контактов, свои сценарии, свои лимиты по тарифу. Со стороны агентства это одна команда, один репозиторий, один деплой. Между этими двумя реальностями нужен мост, который не утечёт из клиента A в клиента B, переживёт ротацию ключей без даунтайма и не превратится в куст if'ов по проектам.

Большинство ошибок multi-tenant лежит не в коде, а в неявных предположениях: «project_id один на запрос», «токен можно положить в общий конфиг», «лимит у всех одинаковый». Каждое однажды оказывается неверным — и тогда клиент A видит данные клиента B, либо вся интеграция упирается в лимит самого слабого тарифа из ста.

Границы доверия. В multi-tenant есть три зоны — интегратор, платформа, клиент платформы — и ключи как механизм аутентификации эти границы должны явно отражать. Первый паттерн — API-ключ на проект: интегратор хранит по одному ключу на клиента, ключи выпущены через клиентский кабинет. Просто и предсказуемо, но для каждого нового клиента нужен ручной шаг. Второй — OAuth installation: интегратор регистрирует приложение, клиент платформы одним кликом «подключает приложение к своему проекту», на выходе — access_token, привязанный к приложению и к конкретному проекту (стандарт для third-party, как GitHub Apps, Stripe Connect).

В BOTIX доступны оба паттерна. Ключ btx_live_… привязан к project_id явно (btx_api_keys.project_id). OAuth-installations реализованы через таблицы btx_oauth_apps, btx_oauth_auth_codes, btx_oauth_access_tokens (TTL 1 час), btx_oauth_refresh_tokens (TTL 30 дней, ротация при обмене) и btx_oauth_installations для UI «Подключённые приложения». OAuth-токен несёт префикс btx_oauth_, обычный ключ — btx_live_.

Изоляция project_id — главное требование. На уровне платформы: каждый запрос приходит с токеном, токен резолвится в project_id, любой SQL содержит WHERE project_id = ? от текущего токена. В BOTIX это закреплено правилом №6 в CLAUDE.md и проверяется через Auth::assertProjectAccess($projectId) в начале каждого контроллера, работающего с проектом; контекст без сессии ставит Auth::setApiKeyContext($projectId) в middleware PublicApiAuth. Важная деталь дизайна: при попытке достать ресурс чужого проекта возвращается 404, а не 403 — чтобы не утекало само существование объектов.

На уровне интегратора нужна та же дисциплина. Если SDK инициализируется один раз глобально и используется во всех ботах, любая ошибка в параметрах уводит запрос на чужой проект. Правильный паттерн — один инстанс SDK на проект:

python
import botix

# один инстанс SDK на project_id; ключи — из secret manager, не из общего конфига
clients = {
    project_id: botix.Client(api_key=XXXXXXXX(project_id))
    for project_id in active_projects
}

def send_to_project(project_id: int, contact_id: int, text: str):
    client = clients[project_id]
    client.messages.create(contact_id=contact_id, text=text)

Инстансы кэшируются на старте, ключи берутся из защищённого хранилища, и в момент вызова невозможно случайно перепутать project_id с api_key. Если у одного project_id ключ ротируется — пересоздаётся только один инстанс, остальные продолжают работать. Почему SDK при этом не тянет 14 зависимостей и как это влияет на multi-tenant-деплой — в разборе Zero-dependency SDK в open-source: стратегия платформы.

Scopes — третий слой защиты. По умолчанию ключ может всё, что доступно тарифу проекта. На практике это редко нужно: сервису отправки сообщений не нужны права на удаление контактов. BOTIX поддерживает scopes на уровне ключа (btx_api_keys.scopes, JSON) и на уровне OAuth-токена (btx_oauth_access_tokens.scopes). Middleware Public API при каждом запросе проверяет, что эндпоинт входит в scope токена; несоответствие — 403 INSUFFICIENT_SCOPE. Список реальных scope: contacts:read, contacts:write, messages:read, messages:send, scenarios:read, scenarios:run, chats:read, channels:read, webhooks:read, webhooks:write.

На стороне интегратора это значит: на каждый изолированный кусок логики — отдельный ключ с минимальным scope. Сервис рассылки — ключ с messages:send. Аналитический воркер, раз в час читающий историю, — отдельный ключ с messages:read. Если оба сервиса упадут в общий лог, ни один из ключей не даст компрометации полного контура.

Ротация без даунтайма. Плановый ротейт раз в полгода — гигиена, внеплановый после утечки — обязателен. У BOTIX два состояния ключа через btx_api_keys.status (active / revoked) и поле revoked_at: выпускается новый ключ, инстанс SDK переключается на него, затем старый помечается revoked. Все три шага независимы. Отдельно есть жёсткий хук: при отзыве подписки или переходе на тариф с api_enabled=0 все ключи проекта авто-revoked (реализация в confirmInternal).

Audit-логи. Финальный слой — журнал всех вызовов: btx_api_log пишет на каждый запрос метод, эндпоинт, статус-код, project_id, response_time_ms и request_id (UUID); retention 90 дней, фильтр по ключу и статусу — на странице API-ключей клиента (/settings?tab=api-keys). Журнал решает три задачи: отладку, расследование инцидентов и оптимизацию (что вызывается чаще всего — может, пора на webhook).

Чек-лист архитектора multi-tenant:

  1. Ключи хранятся отдельно на каждый project_id и кэшируются на старте процесса.
  2. Scope каждого ключа сужен до минимально необходимого.
  3. Каждая операция явно указывает project_id — нет глобального «текущего проекта».
  4. Ротация ключа возможна без остановки обработчика.
  5. Журнал API-вызовов читается из платформы и параллельно пишется внутри интегратора.

Пропущен хоть один пункт — архитектура работает только до первого инцидента.

White-label: что нужно от API, чтобы стать платформой для партнёров

Multi-tenant решает задачу одного интегратора со ста клиентами. White-label — следующий уровень: партнёр встраивает API внутрь своего продукта и продаёт его дальше под своим брендом. Партнёр — агентство под своим брендом, ISV со своим SaaS, системный интегратор, собирающий решения из чужих кирпичей.

Чем партнёрская модель отличается от прямого использования. У прямого клиента контур простой: один аккаунт, один проект, несколько ключей, один webhook. У партнёра — многоуровневый: сам партнёр имеет аккаунт у поставщика, внутри него десятки-сотни конечных клиентов, у каждого свой логический проект со своими данными, биллингом, webhook'ом. Обычный ключ работает в контексте одного проекта — партнёрскому нужно работать в контексте партнёрского аккаунта и переключаться между конечными. Скрыть это под тем же эндпоинтом — путь к ошибкам.

Минимальный набор свойств, без которых API не станет платформой:

  • Партнёрский уровень аутентификации — отдельный тип ключа с правами создавать проекты, перечислять их, получать ключи для каждого. Обращение к партнёрским операциям обычным ключом должно возвращать 403.
  • Sub-проекты под одним партнёрским аккаунтом — иерархия «партнёр сверху, конечные проекты снизу», каждый со своими данными, webhook'ом, биллингом.
  • OAuth-installations с реселлерскими scope — чтобы приложение, установленное в партнёрский аккаунт, видело все sub-проекты, а не только тот, куда установлено напрямую.
  • Биллинг-события через API — сырые usage-данные (запросы, сообщения, активные контакты) по каждому sub-проекту за период: партнёр биллит по своей модели.
  • Audit-логи на партнёрском уровне — когда конечный клиент пишет «не работает», партнёр диагностирует сам, не привлекая поставщика.
  • Кастомные домены и брендирование — если в решении есть UI, конечный клиент заходит в кабинет под брендом партнёра.

Изоляция данных — критическая граница. Внутри одного партнёрского аккаунта могут жить sub-проекты конкурирующих компаний. Возможность одного увидеть данные другого — катастрофа. Граница нужна на трёх уровнях: БД (WHERE project_id = ? в каждом запросе), API (явный project_id + проверка принадлежности партнёрскому аккаунту), ключи (нельзя получить ответ по одному sub-проекту, передав ключ другого). В BOTIX базовый слой этой защиты уже боевой — тот же Auth::assertProjectAccess($projectId) в начале каждого эндпоинта.

Что из этого реально в BOTIX сегодня — честно. Партнёрский слой в BOTIX собран не целиком. Что работает в проде: OAuth-installations (btx_oauth_installations) — третье приложение регистрируется, конечный клиент устанавливает его в свой проект, аудит пишется в btx_api_log с колонкой oauth_app_id (какое приложение что делало от имени какого пользователя). Отдельно живёт реферальная партнёрская программа (ставка 20% на 12 месяцев, referral_code, таблицы btx_partner_*) — но это модель «привёл клиента — получаешь процент», а не технический white-label. Полноценный partner-API с sub-проектами, партнёрскими ключами и GET /partner/usage — в roadmap, публичного релиза нет. Текущая партнёрская работа идёт через OAuth-installations и per-project ключи, без кастомного фронтенда. Это ограничение, а не фича, и мы его не прячем.

Чего делать не стоит (грабли, на которые наступают все): не навешивать партнёрские возможности на обычные эндпоинты флагами «если ты партнёр, передай ещё параметр» — лучше отдельный префикс /partner/v1/*; не делать партнёрский API «по запросу избранным под NDA» — работает, пока партнёров три, на двадцати начинаются разные SLA и теневая документация; не ставить высокий порог входа — на старте партнёрский слой это маленькие команды, из которых вырастают крупные. Решение о партнёрском слое — продуктовое, не техническое: не каждый API должен быть платформой. Но если решение принято, семь свойств выше закладываются с первого дня, а не на третьей итерации после жалоб. Как генерировать партнёрские SDK из единой спецификации — в разборе SDK из OpenAPI: генератор, white-label, open-source.

Open-source SDK: security-аудит без NDA, bug fix через PR, сигнал зрелости

Третье свойство платформы — открытый SDK. В большинстве команд вопрос «открывать ли исходник» обсуждают через категорию «щедрость»: «принесём пользу сообществу» против «лучше потратим время на фичи». Так обсуждение не приходит к решению, потому что стороны рассуждают о морали, а не о бизнесе. На деле open-source SDK — инструмент с конкретными функциями.

Функция 1. Security-аудит без участия поставщика. Корпоративная интеграция почти всегда проходит через security-команду. Закрытая библиотека — чёрный ящик, который что-то делает с данными и куда-то их отправляет. Три исхода: заблокировать интеграцию (частый), потребовать SOC 2 Type II + DPA (десятки тысяч долларов и месяцы), попросить исходник под NDA (недели). Open-source закрывает все три: security открывает GitHub-репозиторий, видит, что библиотека делает только заявленное, и пропускает. SOC 2 всё равно нужен для инфраструктуры и процессов, но SDK как точка входа в код корпорации становится прозрачным.

Три SDK BOTIX выложены в организацию BOTIX-pro на GitHub под открытой лицензией:

bash
composer require botix-pro/sdk:^1.1
pip install botix==1.1.0
npm install @botix/sdk@1.1.0

Каждый репозиторий — sdk-php, sdk-python, sdk-node — содержит полный исходник: никакого обфусцированного клиента, никаких бинарных модулей. Security читает код за час.

Функция 2. Bug fix не блокируется вендором. В закрытом SDK любая ошибка — тикет в поддержку, новая версия через месяц-три, всё это время у интегратора костыль. В open-source команда сама делает PR либо временно патчит через форк по VCS-ссылке:

json
{
  "require": { "botix-pro/sdk": "dev-fix-retry-bug" },
  "repositories": [{ "type": "vcs", "url": "https://github.com/my-org/sdk-php-fork" }]
}

Никакой блокировки. Хороший фикс уходит в upstream PR через неделю — и достаётся всем интеграторам.

Функция 3. Сигнал зрелости API. Публикация SDK на GitHub — сигнал стабильного контракта, который не стыдно показать. Закрытый SDK читается как «наколхозили, поэтому прячут». Сигнал работает не на сознательном уровне: покупатель говорит «знакомая компания, серьёзные люди» после того, как его инженер посмотрел репозиторий и не нашёл ничего стыдного. В BOTIX это встроено с старта: три SDK с одинаковой структурой методов, три example-репозитория (example-laravel, example-express, example-flask), CLI botix-cli в PyPI. Структура client.contacts.create(...) одинакова в PHP, Python и Node — переключаясь между языками, разработчику не нужно переучиваться.

Функция 4. Канал контрибьюций. На ранних этапах open-source SDK — односторонняя отдача: поставщик пишет, сообщество потребляет. По мере роста появляются PR с типичными улучшениями (retry, timeout, прокси, совместимость с версией зависимости) — часы, которые поставщик не потратил. У зрелых SDK (Stripe, Twilio, Slack) — десятки внешних контрибьюторов; у молодых — единицы или ноль, и это нормально. SDK BOTIX молодые, эффект оценим через 90 дней после релиза. Бонус — issue tracker как форма документации: ответ на неочевидное поведение остаётся в архиве GitHub, и через полгода следующий интегратор находит его поиском, не открывая нового тикета.

Страхи против open-source — что с ними не так. «Код утащат» — утащить SDK без API невозможно, а под свой API конкуренту переписать легче, чем переиспользовать чужой. «Раскроется архитектура» — решается дисциплиной: SDK делает три вещи (формирует запрос, подписывает, парсит ответ), бизнес-логика и расчёты — на стороне API; открытый SDK не раскрывает ничего сверх того, что и так в OpenAPI-спецификации. «Поддержка станет дороже» — реальная работа, но без неё open-source SDK превращается в кладбище с просрочкой по 200 дней; в BOTIX issue tracker пока почти пустой, с ростом — SLA на triage 48 часов.

Что в BOTIX: все SDK и CLI

Все три SDK и CLI — на GitHub в организации BOTIX-pro:

РепоПакет
sdk-phpComposer botix-pro/sdk
sdk-pythonPyPI botix
sdk-nodenpm @botix/sdk
cliPyPI botix-cli
example-laravelПример Laravel 11 + Pest
example-expressПример Express 4 + TypeScript + Vitest
example-flaskПример Flask 3 + pytest

SDK сгенерированы из единого openapi.yaml (v1.1.0) через openapi-generator-cli — структура методов одинакова на всех трёх языках. Зависимостей нет: PHP использует ext-curl, Python — urllib.request, Node — встроенные https/http. composer audit, npm audit, safety check показывают зелёный статус не из-за везения, а по дизайну. Почему generator, а не ручные SDK, и как это удерживает три языка в синхроне — в разборе SDK из OpenAPI: генератор, white-label, open-source.

Пакеты публикуются в стандартные менеджеры:

bash
composer require botix-pro/sdk:^1.1
pip install botix==1.1.0
npm install @botix/sdk@1.1.0
pip install botix-cli

Когда всё это не нужно

Открытый SDK и партнёрский слой — не для каждого API. Отложить стоит, если: API нишевой и вся аудитория по NDA; API в стадии активного breaking-change (нестабильность в публичном репозитории даёт обратный сигнал); нет человека на review PR и ответы на issues. Все три — про ранние стадии. Когда продукт зреет и ресурс появляется, открытие и платформизация становятся логичным шагом — и именно тогда закладывают процесс: лицензию, contribution guide, регламент релизов, отдельную документацию. Полный список публичных пакетов — на developers.botix.pro; спецификация — developers.botix.pro/openapi.yaml.