SDK BOTIX 1.1: zero-deps, генерация из OpenAPI и триал
Вышло обновление публичных SDK BOTIX — версия 1.1 для PHP, Python и Node. TL;DR: три архитектурных изменения — ноль транзитивных зависимостей, генерация клиентов из единого `openapi.yaml` и триал-режим вместо отдельного sandbox-сервера. Ниже разбираю каждое по сути: зачем так сделано, что это даёт интегратору и как мигрировать с 1.0 без breaking changes.
Коротко
- Zero-deps. Все три SDK переведены на stdlib своих языков (
ext-curl/urllib.request/ встроенныйhttps). Секцияdependenciesпустая —composer audit/npm audit/safety checkзелёные по дизайну, а не по везению. - Генерация из
openapi.yaml. Единственный источник правды — спека в репозитории платформы. Из неё генерируются PHP/Python/Node SDK, Redoc-документация, Swagger UI и Postman/Bruno-коллекции. Никакой рассинхронизации трёх ручных клиентов. - Триал вместо sandbox. Отдельного тестового контура нет. Разработчик тестирует тот же production-контур, изолированный на уровне проекта и ограниченный лимитами тарифа. Класс ошибок «в sandbox работало, в проде нет» убран архитектурно.
Установка
Команды установки прежние, версия — 1.1.0:
composer require botix-pro/sdk:^1.1
pip install botix==1.1.0
npm install @botix/sdk@1.1.0Исходники открыты в организации github.com/BOTIX-pro — репозитории sdk-php, sdk-python, sdk-node. Лицензия открытая, PR принимаются. Эндпоинты не изменились — базовый префикс по-прежнему https://api.botix.pro/public/v1/, авторизация — Authorization: Bearer XXXXXXXX… (ключ выдаётся в кабинете и показывается один раз).
Тезис 1. Ноль транзитивных зависимостей — и почему это про риск, а не про эстетику
SDK для публичного API — это не приложение, а тонкая обёртка над HTTP. Каждая её зависимость становится головной болью того, кто её установит. Заходишь на страницу документации, видишь pip install/composer require/npm install, ставишь — и через минуту в requirements.txt или composer.lock появляется не одна новая строка, а четырнадцать: SDK притащил HTTP-клиент, JSON-валидатор, UUID-библиотеку, свой retry-механизм, ещё один логгер и пару утилитарных пакетов от одного мейнтейнера «в свободное время». Каждая из них создаёт ровно три класса проблем.
Конфликты версий. SDK тянет requests==2.28.0, у интегратора в проекте уже стоит requests==2.31.0 из-за собственного фреймворка. При жёстком pin установка падает конфликтом; при мягком (>=2.20,<3.0) — встанет одна из совместимых версий, и обновление фреймворка через полгода может сломать совместимость с SDK. В Python это особенно больно: глобальное пространство имён не позволяет держать две версии requests одновременно. В Node чуть лучше за счёт node_modules per-package, но дубли раздувают бандл.
Supply-chain атаки. Каждая транзитивная зависимость — вектор атаки. История полна примеров: event-stream (мейнтейнер отдал доступ незнакомцу, тот добавил кражу криптокошельков), ua-parser-js (взлом аккаунта, релиз с майнером), colors.js («протест-релиз», ломающий всех зависимых). Когда SDK тянет 14 прямых зависимостей, общее дерево транзитивных пакетов легко переваливает за 100, и npm audit / safety check начинают регулярно ругаться на advisories в зависимостях зависимостей — которые интегратор пофиксить не может, потому что не контролирует SDK.
Устаревание. Зависимости стареют. Свежая год назад сегодня имеет известную уязвимость. Активно поддерживаемый SDK обновляет их вовремя; заброшенный — превращает каждую зависимость в мину под интегратором.
Альтернатива — zero-dependency SDK: только стандартная библиотека языка, ничего больше. В BOTIX так построены все три клиента:
| SDK | Что используется | Что убрано |
|---|---|---|
| PHP | ext-curl из стандартной поставки | guzzlehttp/guzzle, psr/log |
| Python | urllib.request из stdlib | requests, urllib3 |
| Node | встроенные модули https, http | axios, node-fetch |
composer.json PHP-SDK не имеет ни одной зависимости в секции require кроме php и ext-curl. У Python-SDK пустой requirements.txt. У Node-SDK нулевая секция dependencies (только devDependencies под билд, в продакшен не попадают).
Что это даёт интегратору. composer audit, npm audit, safety check показывают зелёный статус по дизайну. Размер бандла Node-приложения после установки @botix/sdk увеличивается на 30-40 КБ, а не на 2 МБ. Время старта не страдает от dynamic-import тонн транзитивных модулей.
Возражение «requests удобнее urllib, зачем городить с stdlib» справедливо для прикладного кода. Но SDK — не прикладной код: он живёт у интегратора в продакшене, его задача — минимизировать риск, а не быть красивым внутри. HTTP-клиент из stdlib покрывает 95% сценариев (GET/POST/PUT/DELETE, заголовки, тело, чтение ответа); HTTP/2-мультиплексирование и connection pooling публичному API через интернет не нужны — HTTP/1.1 работает без потерь. Правило перед установкой любого чужого SDK простое: открой его package.json / composer.json / setup.py на GitHub и посмотри секцию dependencies. 0-2 пакета — уважение к интегратору, 5-7 — приемлемо, 10+ — груз чужой экосистемы. Подробнее про сам принцип и открытый код — в разборе Zero-deps SDK: генерация из OpenAPI и open-source.
Структура SDK одинаковая во всех трёх языках — метод client.contacts.create(...) есть везде, аргументы те же, ответ той же структуры:
from botix import Client
client = Client(api_key=XXXXXXXX["BOTIX_API_KEY"])
contact = client.contacts.create(phone="+79991234567", name="Test")const { Client } = require('@botix/sdk');
const client = new Client({ apiKey: XXXXXXXX });
const contact = await client.contacts.create({ phone: '+79991234567', name: 'Test' });При переключении между языками переучиваться не нужно. Как эта идентичность вообще достигается — второй тезис.
Тезис 2. Генерация из openapi.yaml вместо трёх ручных SDK
Версия 1.0 писалась руками: PHP отдельной командой, Python отдельной, Node отдельной. К концу первого квартала эксплуатации стало понятно, что три параллельные команды держат паритет с трудом — на каждое изменение API уходило три PR с разной скоростью. Ручной SDK ломается тремя способами, и все три неизбежны.
Documentation drift. API получает новый endpoint, его добавляют в код сервера и в документацию. Через две недели дописывают в PHP-SDK, через месяц — в Python, через два — в Node. В этом промежутке клиенты на разных языках видят разный набор возможностей, а на странице документации метод уже есть — и прилетает тикет «у вас в доках метод X, а в SDK его нет».
Type drift. Сервер отдаёт created_at строкой ISO 8601. В PHP его типизировали как string, в Python — как datetime, в Node — как Date | string. Меняется формат на Unix timestamp — на сервере один коммит, в трёх SDK три PR, три релиза, три обновления доков, и синхронно они не пройдут почти никогда.
Coverage drift. Сервер имеет 60 endpoints. PHP-SDK покрывает 60, Python — 55, Node — 47. Держать паритет ручным трудом реально только с отдельным человеком, у которого это единственная задача.
В 1.1 единственный источник правды — файл openapi.yaml в репозитории платформы. Он описывает каждый endpoint, модель, код ошибки и поле в формате OpenAPI. Из него генерируется всё:
- SDK на PHP через
openapi-generator-cli; - SDK на Python через тот же инструмент;
- SDK на Node через тот же инструмент;
- публичная документация Redoc на
developers.botix.pro; - Swagger UI на
developers.botix.pro/try-itдля пробных запросов из браузера; - Postman-коллекция и Bruno-коллекция для CLI-тестирования.
Один файл — набор артефактов. Любое изменение API распространяется через генератор за минуту, без копипасты и рассинхронизации.
Отдельная история — код-сэмплы в документации. Обычно они лежат в Markdown и синхронизируются с API вручную (то есть никогда до конца). В OpenAPI есть расширение x-codeSamples — примеры хранятся прямо в спеке, под каждым endpoint:
paths:
/contacts:
post:
x-codeSamples:
- lang: curl
source: |
curl -X POST https://api.botix.pro/public/v1/contacts \
-H "Authorization: Bearer $BOTIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"phone":"+79991234567","name":"Test"}'
- lang: PHP
source: |
$contact = $client->contacts()->create([
'phone' => '+79991234567',
'name' => 'Test',
]);На момент выхода 1.1.0 таких блоков внутри openapi.yaml — 92, по три-четыре языка (curl, PHP, Python, Node) на каждый осмысленный endpoint. Меняется сигнатура метода — сэмпл правится в одном месте и через рендер Redoc сам попадает на страницу документации.
Что держать в голове при генерации. Сгенерированный SDK — не «бесплатное решение под ключ»:
- Качество спеки определяет качество SDK.
oneOfбезdiscriminatorне даст корректный union-тип в TypeScript; код ошибки, указанный вdescription, но не вresponses, клиент не научится обрабатывать. Любая неточность всплывёт во всех SDK одновременно. - Шаблоны генератора — точка кастомизации. Нет strict types в стандартном PHP-шаблоне или timing-safe сравнения в
verifyWebhook— правится один раз в шаблоне, а не руками в сгенерированном коде; при следующей регенерации правки сохранятся. - Ручные надстройки живут отдельно. Нестандартные паттерны (long-polling с особой логикой реконнекта, batch-склейка запросов, Laravel facade / Django app) кладутся поверх генерации: сгенерированное — в
src/Generated/(перезаписывается целиком), ручное — вsrc/Extension/(остаётся нетронутым). Конфликт становится явным при сборке. - Тесты пишутся отдельно. Генератор покрывает структуру — типы, сигнатуры, сериализацию. Поведенческие тесты (что метод шлёт правильный запрос и верно читает ответ) пишутся руками, но их объём в разы меньше объёма ручного SDK.
Итог тезиса: любой ручной SDK — технический долг с первого дня, растущий пропорционально числу языков, endpoints и скорости изменений API. Спека — не «работа сверху», а центральный артефакт, из которого вытекает остальное.
Тезис 3. Триал вместо отдельного sandbox
Версия 1.0 предполагала, что для тестирования нужен отдельный sandbox: отдельный домен, отдельная база, отдельные ключи, отдельная панель. Логика понятная — дать разработчику место, где можно ломать всё, не задевая боевые данные. Проблема в том, что любой второй контур — это не половина прода, а отдельный продукт со своей жизнью, которая со временем расходится с продом:
- Дублирование инфраструктуры — своим серверам БД, очередям, кешу и воркерам sandbox стоит денег; экономят — тайминги другие, тесты ненадёжны.
- Дублирование миграций и фич — каждую надо применить дважды, команда забывает, через полгода в sandbox устаревшие поля и старая логика ошибок, заводится тикет «синхронизировать sandbox с prod».
- Расхождение поведения — самый болезненный пункт: sandbox считают «безопасным», ослабляют rate-limit, выключают fraud-фильтры, смягчают валидацию. Интеграция работает в sandbox, переключается на prod — падает на первом запросе. Это не лень разработчика, а архитектурное расхождение, заложенное платформой.
В 1.1 отдельного sandbox-окружения нет вовсе. Вместо него — триальный кабинет, который физически живёт на том же production-контуре, но имеет жёсткие лимиты и флаги, ограничивающие побочные эффекты. На практике это значит:
- триальный проект пишет в ту же базу, что и платный;
- запросы идут через те же FPM-воркеры, те же FastAPI-сервисы, ту же Redis-очередь;
- те же миграции, та же логика обработки ошибок, те же коды ошибок, те же заголовки в ответе.
Различия — только в трёх местах:
- Лимиты тарифа. Поле
api_rate_per_minuteдля триала ниже (30/мин против 2000/мин на тарифе «Бизнес»). Это тот же механизм rate-limit, что и у платных клиентов, просто с более жёстким порогом — разработчик на триале сталкивается ровно с тем rate-limit, что увидят его пользователи на проде. - Toggle on-write операций. В кабинете доступен
api_write_enabled = false— read-only режим: все POST/PUT/DELETE возвращают403 TRIAL_READ_ONLY. Удобно для смоук-тестов чтения без риска что-то изменить. Тот же тогл есть и у платных клиентов. - Срок жизни. Триал — 14 дней без карты. После этого кабинет переходит в архивное состояние (read-only по умолчанию, до оплаты). Не успел протестировать — заводишь второй триал или оплачиваешь минимальный тариф.
Регистрация developer-триала — на app.botix.pro/developers/quickstart. После регистрации сразу выдаётся API-ключ, без мастера настройки (мастер нужен владельцу бизнеса, а не разработчику).
# Триал — те же эндпоинты, тот же формат
curl -X POST https://api.botix.pro/public/v1/contacts \
-H "Authorization: Bearer XXXXXXXX…" \
-H "Content-Type: application/json" \
-d '{"phone":"+79991234567","name":"Test"}'Почему это работает: разработчик тестирует ту же систему, которую увидит на проде, — не её модель, не урезанную копию, не сборку трёхмесячной давности. Это убирает целый класс ошибок: «поле было в sandbox, в проде нет» — невозможно (контур один); «в sandbox 200, в проде 422» — невозможно (валидаторы те же); «webhook в sandbox шлётся, в проде нет» — невозможно (обработчик тот же); «заголовок X-RateLimit в sandbox другого формата» — невозможно (middleware один). Бонусом команда платформы тратит ноль усилий на синхронизацию контуров — синхронизировать нечего.
Граница «триал/прод» проходит не в инфраструктуре, а в данных и правах: триальный проект — это запись в btx_projects с флагом is_developer_trial = true и тарифом «триал» со своими limits. API-ключ триала видит только данные своего проекта — так же, как любой другой ключ, через ту же Auth::assertProjectAccess(). Разработчик может делать с API что угодно (создавать контакты, слать webhook, перегружать запросами), но воздействует только на свой проект.
Сравнение по затратам команды:
| Затрата | dev/staging/prod | Триал-как-sandbox |
|---|---|---|
| Серверы | х2-х3 | х1 |
| Миграции | Каждую применить дважды | Один раз |
| Тестовые данные | Поддерживать отдельно | Не нужны |
| Документация | «Различия между sandbox и prod» | Не нужна |
| Тикеты «в sandbox работает, в prod нет» | Регулярно | Не возникают |
Где подход неприменим — честно, случаи есть. Если каждый POST реально двигает деньги (/payments/charge), нужен тестовый ключ эквайринга или test-mode на уровне ключа. Если API шлёт SMS/push/email — рассылка на боевые номера недопустима, нужен «test recipient» или режим «логировать, но не доставлять». Если API управляет физическим оборудованием — тестировать на проде нельзя в принципе, тут dev/staging/prod единственный путь. В BOTIX эти случаи закрываются флагами в самих модулях: у bot-preview есть $context['preview']=true — модуль пишет в лог, но не отправляет реальное сообщение в Telegram. Это снимает риск побочных эффектов без второго контура. Пошаговый онбординг на триале разбираем отдельно — первый день с API за 7 шагов и квикстарт с триал-sandbox и sub-проектами.
Что ещё приехало в 1.1
Помимо трёх главных изменений, в релиз попали два helper'а, которые раньше каждый интегратор писал руками.
Webhook helper с verify + timestamp. В 1.0 проверка подписи сводилась к ручному HMAC, где повторялись две ошибки: сравнение через == вместо compare_digest (уязвимо к timing-атакам) и игнорирование X-BOTIX-Timestamp (открыта дверь replay-атакам). В 1.1 во всех трёх SDK есть готовый helper:
from botix import Webhook
if not Webhook.verify(secret, raw_body, signature_header, timestamp_header):
return Response(status=401)Внутри — сравнение в постоянном времени, проверка timestamp на свежесть в окне ±5 минут, единый формат заголовка. Аналогично в PHP (Botix\SDK\Webhook::verify) и Node (Webhook.verify).
Автоматический retry с экспоненциальным backoff. В 1.0 при 429 Too Many Requests SDK возвращал ошибку, и retry-логика была на стороне интегратора. В 1.1 ретраи встроены: по умолчанию 5 попыток с экспоненциальной задержкой и jitter, читает Retry-After из ответа платформы, не повторяет 4xx кроме 429:
from botix import Client, RetryConfig
client = Client(api_key=..., retry=RetryConfig(max_attempts=5, jitter=True))
# отключить retry: RetryConfig(max_attempts=1)Оба слоя разбираем глубже в связке про безопасный обработчик и продакшен-надёжность — SDK без зависимостей и webhook-симулятор.
Как мигрировать с 1.0
Бинарной несовместимости нет — все методы 1.0 работают в 1.1 без изменений. Что нужно сделать:
- Обновить пакет:
composer update botix-pro/sdk,pip install --upgrade botix,npm update @botix/sdk. - Если код проверяет webhook-подпись вручную — переключиться на
Webhook::verify()из SDK. Старый код продолжит работать, но в нём с большой вероятностью есть timing-attack-уязвимость. - Если код пишет собственный retry-цикл — отключить его или оставить как есть (SDK не дублирует retry поверх существующего).
- Если используется sandbox-домен (был только в pre-release 1.0) — переключиться на основной
api.botix.pro/public/v1/*с триал-ключом.
Полная инструкция миграции — в каждом SDK-репозитории файл MIGRATION.md. Breaking changes на 1.0 → 1.1 нет. Про semver самих SDK и обновления без сюрпризов — версии SDK и semver.
Что осталось как было
Эндпоинты не менялись — api.botix.pro/public/v1/*. Идемпотентность по-прежнему обязательна на мутирующих эндпоинтах через заголовок Idempotency-Key. Audit-лог в кабинете на app.botix.pro/developers показывает все запросы со SDK 1.0 и 1.1 одинаково.
Версионирование SDK не совпадает с версионированием API — это сознательно. API сейчас в 1.1.0, SDK тоже 1.1.0, но дальше они могут расходиться: например, SDK 1.1.5 может работать с API 1.1.0 (патчи только SDK, без изменений контракта). Связь поддерживается через MIGRATION.md в каждом репозитории.
Попробуйте API BOTIX
Получите ключ, отправьте первый запрос и поймайте webhook — прямо в браузере, без боевого канала.
Попробовать API бесплатноЧитайте также
Почему в SDK ноль зависимостей и генерация из OpenAPI
Первый запрос к API за 5 минут: SDK из OpenAPI и триал
Онбординг разработчика: quickstart, sandbox и API-ключи
Zero-dependency SDK, open-source и партнёрский API
