Первый запрос к API за 5 минут: SDK из OpenAPI и триал
TL;DR: developer-триал пропускает мастер настройки и сразу отдаёт ключ на странице quickstart. Первый успешный запрос — `GET /public/v1/me` — виден в терминале за пять минут: реальный JSON от `api.botix.pro`, а не заглушка sandbox. Дальше — почему SDK для PHP, Python и Node генерируются из одного `openapi.yaml`, и почему роль sandbox выполняет сам триал на боевом контуре.
Коротко
- Quickstart — отдельный жанр, не короткая версия доки: первый успешный запрос за пять минут решает, останется разработчик или закроет вкладку. Ключ выдаётся прямо на странице quickstart, а developer-триал (
is_developer_trial) пропускает мастер настройки бизнеса. - SDK не пишутся руками, а генерируются из единого
openapi.yaml(OpenAPI 3.0) → PHP, Python, Node + Redoc + Swagger UI. Single source of truth убирает documentation-, type- и coverage-drift. - Отдельного sandbox нет: триал живёт на том же production-контуре, изолирован на уровне проекта и отличается только лимитами, тоглом записи (
api_write_enabled) и сроком (14 дней). На триале запись по умолчанию выключена — мутирующие методы возвращают403 TRIAL_READ_ONLY, поэтому первый успешный запрос делаем на чтение.
Что делаем и почему первые пять минут решают всё
Любой публичный API проходит через одно узкое горлышко — первую встречу с разработчиком, и у неё нет второго шанса. Если на первый осмысленный запрос уходит больше пяти-десяти минут, вкладку закрывают: появится другая задача, коллега предложит другое решение, ссылка потеряется в закладках. Это не лень, а фокус — разработчик пришёл проверить гипотезу «подходит ли этот API под мою задачу», и каждая лишняя минута снижает шанс, что гипотеза вообще будет проверена.
Отсюда главная мысль: quickstart — это не сокращённая документация, а отдельный артефакт со своим жанром. Документация отвечает на вопрос «как устроена система», её читают последовательно и возвращаются к ней. Quickstart отвечает на вопрос «как получить первый ответ прямо сейчас», его читают один раз и по диагонали. Признаки правильного quickstart, которые проверяются глазами:
- Первый блок кода виден без скролла, на первом экране после заголовка.
- API-ключ выдаётся на этой же странице — без «зарегистрируйтесь, подтвердите email, дождитесь письма, найдите вкладку API».
- Snippet содержит реальные значения, а не плейсхолдеры
YOUR_API_KEY/https://api.example.com. - Есть переключатель языка: curl и два-три языка с SDK.
- Между копированием кода и видимым результатом нет промежуточных шагов.
Единственная честная метрика такого quickstart — медианное время от просмотра страницы до первого успешного запроса с того же ключа. Меньше пяти минут — жанр выдержан. Больше — где-то трение. Просмотры, время на странице, глубина прокрутки вторичны: quickstart нужен не чтобы его читали, а чтобы после него разработчик сделал работающий запрос.
Дальше — ровно этот путь по шагам: выпустить ключ, поставить SDK, получить в консоли реальный JSON от api.botix.pro.
Шаг 1. Developer-триал без мастера настройки
В обычном онбординге BOTIX триал начинается с мастера: ниша, профиль бизнеса, подключение Telegram, импорт каталога, приветственный сценарий. Это нужно владельцу бизнеса — его задача увидеть работающего бота под свою нишу. У разработчика задача другая: понять форматы запросов и ответов, проверить подпись webhook, оценить контракт ошибок. Мастер для этого — препятствие.
Поэтому при регистрации с пометкой «я разработчик» (флаг is_developer_trial в проекте) система обходит мастер и сразу открывает app.botix.pro/developers/quickstart. На странице — выданный API-ключ (показывается один раз), четыре snippet-таба (curl, PHP, Python, Node) и ссылки на полную документацию и Swagger UI. Всё.
Триал — 14 дней без карты. Отдельного sandbox-окружения нет: триальный проект живёт на том же боевом контуре, что и платные (об этом — в разделе про триал-как-sandbox ниже, а также в разборе когда webhook, а когда polling на триале). Различие — только в значениях лимитов тарифа: rate-limit ниже, а коды ошибок, заголовки и логика валидации идентичны платным. Код с триала переедет в прод без сюрпризов.
Шаг 2. Ключ из quickstart: один показ и кнопка «Перевыпустить»
Полный ключ (btx_live_<40 символов>) показывается на странице quickstart один раз. В БД (btx_api_keys) хранится только bcrypt-хеш (cost 12), сравнение идёт через password_verify + hash_equals — защита от timing-атак; отдельно лежит key_prefix из 12 символов для отображения и поиска. Восстановить полный ключ нельзя — только перевыпустить. Не успели скопировать — рядом кнопка «Перевыпустить», которая аннулирует старый и сразу показывает новый.
Ключ — это секрет, в коде ему не место. Кладём в переменную окружения:
export BOTIX_API_KEY="btx_live_XXXX…"В продакшене — через secret manager (Vault, Yandex Lockbox) с инжекцией в ENV при деплое; локально достаточно export. Антипаттерны хранения ключей и рабочие решения — в отдельном разборе хранение API-ключей в приложении. Ключ в query-string (?api_key=…) платформой не принимается — единственный способ передать его — заголовок Authorization: Bearer, чтобы ключи не попадали в access.log и Referer.
Шаг 3. Установка SDK
Python-SDK ставится одной командой:
pip install botixВерсия — 1.1.0, синхронна с версией API. SDK построен как zero-dependency: тянет минимум сверх стандартной библиотеки, не раздувает requirements.txt и не приносит десяток транзитивных пакетов (почему это принципиально — в разборе zero-deps SDK, генерация из OpenAPI и триал). Проверка:
python -c "import botix; print(botix.__version__)"
# 1.1.0Шаг 4. Первый успешный запрос: GET /me
На триале запись по умолчанию выключена (об этом — на шаге 5), поэтому первый успешный запрос делаем на чтение. Идеальный «hello world» — служебный GET /public/v1/me: он возвращает контекст ключа (проект, scopes, тариф, остаток rate-limit) и доказывает, что ключ рабочий.
import os
import botix
client = botix.Client(os.environ["BOTIX_API_KEY"])
result = client.system.me_get()
print(result)Та же операция через curl:
curl -X GET 'https://api.botix.pro/public/v1/me' \
-H 'Authorization: Bearer XXXXXXXX…'В ответ — стандартная обёртка {success, data}:
{
"success": true,
"data": {
"project_id": 1174,
"api_key_id": 42,
"scopes": ["contacts:read", "contacts:write", "messages:send"],
"plan_key": "trial",
"limits": {
"api_enabled": true,
"api_write_enabled": false,
"per_minute": 30,
"per_day": 1000
},
"rate_limit": {
"limit": 30,
"remaining": 29,
"reset_at": "2026-05-22T14:38:00Z"
}
}
}Здесь сразу видно устройство ответов платформы: всё завёрнуто в {success, data, meta} для успеха и в {success: false, error: {code, message, details}} для ошибки — единый контракт на все эндпоинты. И видно ключевое про триал: "api_write_enabled": false. Это значит, что POST/PUT/DELETE пока вернут отказ. Переходим к записи, чтобы это увидеть.
Шаг 5. Запись: POST /contacts и что вернёт триал
Создание контакта — мутирующая операция. На триале с api_write_enabled=0 любой POST/PUT/PATCH/DELETE честно возвращает 403 TRIAL_READ_ONLY. Это не баг quickstart, а сознательная механика тарифа (разбор — в разделе про триал-как-sandbox). Запрос на создание выглядит так (пример из спеки, Idempotency-Key генерируется на клиенте):
curl -X POST 'https://api.botix.pro/public/v1/contacts' \
-H 'Authorization: Bearer XXXXXXXX…' \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Иван Иванов",
"phone": "+79991234567",
"channel": "telegram",
"tags": ["lead"]
}'Тот же вызов через SDK:
contact = client.contacts.contacts_create(
name="Иван Иванов",
phone="+79991234567",
channel="telegram",
tags=["lead"],
)
print(contact)Чтобы запрос прошёл (201 Created), нужен тариф с включённой записью — платный или триал с включённым тоглом записи. Успешный ответ — снова обёртка {success, data}, где data — полная карточка контакта с целочисленным id:
{
"success": true,
"data": {
"id": 84213,
"project_id": 1174,
"first_name": "Иван",
"last_name": "Иванов",
"phone": "+79991234567",
"tags": ["lead"],
"lead_status": "new",
"last_channel": "telegram",
"messages_count": 0,
"created_at": "2026-05-22T14:37:21Z",
"updated_at": null
}
}Три заголовка запроса имеют значение:
Authorization: Bearer btx_live_…— единственный способ передать ключ.Content-Type: application/json— стандартно.Idempotency-Key— опциональный, но рекомендуемый на мутирующих операциях. Поддержан наPOST /messagesиPOST /scenarios/{id}/run; ключ кешируется в Redis на 24 часа (papi:idem:{apiKeyId}:{key}), и при повторе с тем же ключом возвращается тот же результат плюс заголовокIdempotent-Replayed: 1. Кешируется только успех (200/201), не ошибки доставки. Практическое правило: один UUID на одну логическую операцию; retry одного запроса при таймауте — тот же UUID; цикл из 100 контактов — каждому свой. И один нюанс: ключ живёт 24 часа, для повтора в новый календарный день берите новый.
Каждый вызов логируется в btx_api_log с уникальным request_id (UUID, хранится 90 дней). При непонятной ошибке этот request_id передают в поддержку — по нему за секунды поднимается полная трассировка. Для webhook-доставок тот же идентификатор уходит в заголовке X-Botix-Request-Id.
Шаг 6. Обработка ошибок: контракт ошибок как часть API
Ломаем запрос осознанно — POST без phone:
try:
client.contacts.contacts_create(name="Без телефона")
except botix.ApiError as e:
print(e.code) # VALIDATION_ERROR
print(e.details) # {'fields': {'phone': 'is required'}}В HTTP это тело ошибки в той же обёртке:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Проверьте поля запроса",
"details": { "fields": { "phone": "is required" } }
}
}Поле error.code — это enum из фиксированного набора, часть публичного контракта: переименование = breaking change. Основные коды, с которыми сталкивается интегратор:
| Код | HTTP | Когда |
|---|---|---|
MISSING_API_KEY | 401 | Нет заголовка Authorization |
INVALID_API_KEY | 401 | Ключ не существует или подделан |
KEY_REVOKED | 401 | Ключ отозван |
INSUFFICIENT_SCOPE | 403 | У ключа нет нужного scope |
API_NOT_AVAILABLE_ON_PLAN | 403 | Тариф не включает API |
TRIAL_READ_ONLY | 403 | Триал — мутирующие методы запрещены |
RATE_LIMIT_EXCEEDED | 429 | Превышен лимит per-minute / per-day |
VALIDATION_ERROR | 422 | Ошибка валидации полей |
INVALID_JSON | 400 | Некорректный JSON в теле |
CONTACT_NOT_FOUND | 422 | Контакт не найден или не в проекте |
CHANNEL_NOT_SUPPORTED_YET | 422 | Канал есть в системе, но не подключён к API (пока — WhatsApp) |
Полный список с описанием «когда возникает» и «что делать» — на developers.botix.pro/error-codes. Отдельно про лимиты: заголовки X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset приходят в каждом ответе, а не только в 429; на 429 добавляется Retry-After. Это позволяет клиенту тормозить проактивно, ещё до отказа, — например, при X-RateLimit-Remaining < 5.
Что в quickstart намеренно не попало
Quickstart обязан быть коротким и линейным — значит, что-то сознательно вынесено на отдельные страницы. В жанр первого запроса не входят:
- все коды ошибок (страница
error-codes); - проверка HMAC-подписи webhook (отдельный гайд по webhooks);
- OAuth-flow для third-party-приложений;
- идемпотентность во всех деталях (раздел best-practices);
- версионирование (страница versioning).
Каждая тема критична для production, но не для первого запроса. Если затащить их внутрь, страница раздуется до пятисот строк и потеряет жанр. Правильная структура: quickstart короткий, от него отходят ссылки на разделы, которые становятся актуальны после первого успешного запроса.
Почему SDK генерируется из OpenAPI, а не пишется руками
Когда публичный API выходит на нескольких языках, есть два пути. Первый — писать SDK руками: PHP отдельной командой, Python отдельной, Node отдельной. Второй — описать API в OpenAPI-спеке и генерировать SDK инструментом вроде openapi-generator-cli. Ручной путь быстрее на старте и кажется контролируемым, но через год оказывается дорогим, потому что ломается тремя неизбежными способами:
- Documentation drift. Появился новый endpoint. В сервер и доку он попал сразу, в PHP-SDK — через две недели, в Python — через месяц, в Node — через два. В окне между релизами клиенты на разных языках видят разный набор возможностей, и прилетает тикет «в доке метод есть, в SDK нет».
- Type drift.
created_atтипизировали в PHP какstring, в Python какdatetime, в Node какDate | string. Смена формата на сервере — один коммит; в трёх SDK — три PR, три релиза, три обновления доки, и синхронно они не пройдут почти никогда. - Coverage drift. Сервер — 60 endpoints, PHP-SDK покрывает 60, Python 55, Node 47. Новый метод получает три разных приоритета в трёх командах. Держать паритет руками можно только с выделенным человеком, чья единственная задача — паритет.
Подход BOTIX убирает все три: единственный источник правды о Public API — файл openapi.yaml в репозитории платформы (формат OpenAPI 3.0). Он описывает каждый endpoint, модель, код ошибки и поле. Из него генерируется остальное:
- Правка в
openapi.yaml(новый endpoint / поле / код ошибки). openapi-generator-cliс пресетом PHP →sdk-php(BotixPro\Sdk\Client).- То же для Python →
sdk-python(пакетbotix). - То же для Node →
sdk-node(@botix/sdk,BotixClient). - Из той же спеки рендерится Redoc — публичная документация на
developers.botix.pro. - Из той же спеки рендерится Swagger UI на
developers.botix.pro/try-it— интерактивная песочница, где можно подставить Bearer-токен и слать реальные запросы из браузера.
Один файл — шесть артефактов, любое изменение расходится за минуту без копипасты. Отдельная боль обычных доков — код-сэмплы, которые пишут вручную и не успевают синхронизировать. В OpenAPI это решается расширением x-codeSamples — примеры лежат прямо под endpoint внутри спеки:
paths:
/public/v1/contacts:
post:
x-codeSamples:
- lang: 'curl'
source: |
curl -X POST 'https://api.botix.pro/public/v1/contacts' \
-H 'Authorization: Bearer btx_live_<ваш-ключ>' \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Иван Иванов","phone":"+79991234567","channel":"telegram","tags":["lead"]}'
- lang: 'Python'
label: 'Python (botix)'
source: |
import botix
client = botix.Client('btx_live_<ваш-ключ>')
result = client.contacts.contacts_create(name='Иван Иванов', phone='+79991234567', channel='telegram', tags=['lead'])
print(result)Когда меняется сигнатура — сэмпл правится в одном месте и через рендер Redoc сам попадает на страницу документации. Что важно держать в голове при генерации:
- Качество спеки = качество SDK.
oneOfбезdiscriminator— генератор не построит корректный union-тип в TypeScript; код ошибки вdescription, но не вresponses— клиент не научится его обрабатывать. Спека — это контракт, любая неточность всплывёт во всех SDK одновременно. - Шаблоны генератора — точка кастомизации. strict types в PHP, timing-safe сравнение в
verifyWebhook— правится один раз в шаблоне, а не в сгенерированном коде; при регенерации правки сохраняются. - Версия SDK ≠ версия API. API может быть 1.1.0, а SDK — 1.1.2: третья цифра отражает патчи самого SDK. Связь поддерживают через
MIGRATION.mdв каждом SDK-репозитории. - Тесты пишутся отдельно. Генератор покрывает структуру (типы, сигнатуры, сериализацию); поведенческие тесты — руками, но их объём в разы меньше, чем объём ручного SDK.
Полностью генерация не подходит только для нестандартных паттернов — long-polling с особой логикой реконнекта, batch-склейка HTTP-запросов, фреймворковые адаптеры (Laravel facade, Django app). Приём: сгенерированный код в src/Generated/ перезаписывается целиком, ручные надстройки в src/Extension/ остаются; конфликт становится явным при сборке. Итог простой: любой ручной SDK — технический долг с первого дня, растущий пропорционально числу языков, endpoints и скорости изменений. Аккуратная спека — и дальше всё собирается по кнопке; тот же принцип единого контракта нужен и для multi-tenant, и для white-label — см. OpenAPI как основа мульти-тенант и white-label интеграций и зачем open-source-ить SDK.
Триал как sandbox: почему отдельной тестовой инфраструктуры нет
Когда у платформы появляется публичный API, почти автоматически принимается решение «нужен sandbox» — отдельный домен (sandbox.api.example.com), отдельная база, отдельные ключи, отдельный CI. Логика понятная: дать разработчику место, где можно ломать всё. Проблема в том, что любой второй контур — это не половина прода, а отдельный продукт со своей жизнью, которая со временем расходится с продом:
- Дублирование инфраструктуры. Свои серверы БД, очередей, кеша, воркеров. Экономят — sandbox медленнее и отдаёт другие тайминги.
- Дублирование миграций и фич. Каждую фичу — в оба контура. Команда забывает; через полгода в sandbox устаревшие поля и неработающие endpoints, появляется тикет «синхронизировать sandbox с prod».
- Дублирование тестовых данных. Клиенты, заказы, продукты устаревают, ссылочная целостность ломается на наполовину применённых миграциях.
- Расхождение поведения — самый болезненный пункт. В «безопасном» sandbox ослабляют rate-limit, выключают fraud-фильтры, смягчают валидацию. Интеграция работает в sandbox, переключается на prod — и падает на первом запросе. Классическое «у меня работало в sandbox» — это не лень, а архитектурное расхождение, заложенное платформой.
Подход BOTIX другой: отдельного sandbox нет, его роль выполняет триал, физически живущий на том же production-контуре. Триальный проект пишет в ту же базу, что и платный; запросы идут через те же FPM-воркеры, те же FastAPI-сервисы, ту же Redis-очередь; те же миграции, та же логика обработки ошибок, те же коды и заголовки в ответе. Различий ровно три:
- Лимиты тарифа.
api_rate_per_minuteдля триала ниже (в диапазоне от 30 до 2000 в зависимости от тарифа),api_rate_per_day— от 1000 до 1 000 000. Механизм rate-limit тот же, что у платных, просто порог жёстче. - Тогл записи.
api_write_enabled=0превращает кабинет в read-only — все POST/PUT/DELETE возвращают403 TRIAL_READ_ONLY. Удобно для смоук-тестов чтения без риска что-то изменить; тот же тогл доступен и платным клиентам. - Срок жизни. 14 дней, дальше архивное состояние (read-only до оплаты). Не успел — второй триал или минимальный тариф.
Почему это работает: разработчик тестирует ту же систему, которую увидит на проде — не её модель, не урезанную копию, не сборку трёхмесячной давности. Это снимает целый класс ошибок: «поле было в sandbox, в проде нет» невозможно (контур один); «в sandbox 200, в проде 422» невозможно (валидаторы те же); «заголовок X-RateLimit другого формата» невозможно (middleware один). Бонус — команда платформы тратит ноль усилий на синхронизацию контуров, потому что синхронизировать нечего.
Граница между триалом и продом проходит не в инфраструктуре, а в данных и правах. Триальный проект — это запись в btx_projects с флагом is_developer_trial=true и тарифом «триал» со своими значениями в JSON limits. Ключ триала видит только свой проект — как любой другой ключ; чужие данные защищает та же Auth::assertProjectAccess(), что и у платных (при попытке достать чужой ресурс — 404, не 403, чтобы не утекало существование объектов). «Безопасная среда» реализована через изоляцию проекта, а не инфраструктуры — это правильный уровень.
Где подход неприменим — три случая, и во всех BOTIX обходится флагами в модулях, а не вторым контуром:
- Реальное движение денег. POST к
/payments/charge, списывающий реальные средства, требует тестового ключа эквайринга или test-mode на уровне ключа. - Побочные эффекты на третьих лиц. Рассылка тестовых SMS/push/email на боевые номера недопустима — нужен «test recipient» или режим «логировать, но не доставлять». В BOTIX у
bot-previewесть$context['preview']=true: модуль пишет в лог, но не шлёт реальное сообщение в Telegram. - Тяжёлые операции с физическим миром. Управление оборудованием, печать на завод, доставка грузов — тут dev/staging/prod единственный путь.
Итоговое сравнение затрат команды:
| Затрата | dev/staging/prod | Триал-как-sandbox |
|---|---|---|
| Серверы | х2–х3 | х1 |
| Миграции | Каждую применить дважды | Один раз |
| Тестовые данные | Поддерживать отдельно | Не нужны |
| Документация | «Различия sandbox и prod» | Не нужна |
| Тикеты «в sandbox работает, в prod нет» | Регулярно | Не возникают |
Условие, при котором это работает: лимиты должны быть продуманными, а флаги is_developer_trial / api_write_enabled — не специсключениями, а частью общей механики тарифов, одинаковой у всех клиентов. Что тестировать на триале — чтение или запись, webhook или polling — зависит от интеграции; критерии выбора — в разборе polling против webhook для чат-интеграций.
Попробуйте API BOTIX
Получите ключ, отправьте первый запрос и поймайте webhook — прямо в браузере, без боевого канала.
Попробовать API бесплатноЧитайте также
SDK BOTIX 1.1: zero-deps, генерация из OpenAPI и триал
DX публичного API: quickstart, триал, webhook-симулятор
Как надёжно доставлять webhook: подпись, retry, симулятор
Multi-tenant, polling vs webhook и OAuth для агентства
