Попробовать API
</>
Первый запрос к API за 5 минут: SDK из OpenAPI и триал
sdkИнструкция
19 мин

Первый запрос к 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 символов для отображения и поиска. Восстановить полный ключ нельзя — только перевыпустить. Не успели скопировать — рядом кнопка «Перевыпустить», которая аннулирует старый и сразу показывает новый.

Ключ — это секрет, в коде ему не место. Кладём в переменную окружения:

bash
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 ставится одной командой:

bash
pip install botix

Версия — 1.1.0, синхронна с версией API. SDK построен как zero-dependency: тянет минимум сверх стандартной библиотеки, не раздувает requirements.txt и не приносит десяток транзитивных пакетов (почему это принципиально — в разборе zero-deps SDK, генерация из OpenAPI и триал). Проверка:

bash
python -c "import botix; print(botix.__version__)"
# 1.1.0

Шаг 4. Первый успешный запрос: GET /me

На триале запись по умолчанию выключена (об этом — на шаге 5), поэтому первый успешный запрос делаем на чтение. Идеальный «hello world» — служебный GET /public/v1/me: он возвращает контекст ключа (проект, scopes, тариф, остаток rate-limit) и доказывает, что ключ рабочий.

python
import os
import botix

client = botix.Client(os.environ["BOTIX_API_KEY"])

result = client.system.me_get()
print(result)

Та же операция через curl:

bash
curl -X GET 'https://api.botix.pro/public/v1/me' \
  -H 'Authorization: Bearer XXXXXXXX…'

В ответ — стандартная обёртка {success, data}:

json
{
    "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 генерируется на клиенте):

bash
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:

python
contact = client.contacts.contacts_create(
    name="Иван Иванов",
    phone="+79991234567",
    channel="telegram",
    tags=["lead"],
)
print(contact)

Чтобы запрос прошёл (201 Created), нужен тариф с включённой записью — платный или триал с включённым тоглом записи. Успешный ответ — снова обёртка {success, data}, где data — полная карточка контакта с целочисленным id:

json
{
    "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:

python
try:
    client.contacts.contacts_create(name="Без телефона")
except botix.ApiError as e:
    print(e.code)      # VALIDATION_ERROR
    print(e.details)   # {'fields': {'phone': 'is required'}}

В HTTP это тело ошибки в той же обёртке:

json
{
    "success": false,
    "error": {
        "code": "VALIDATION_ERROR",
        "message": "Проверьте поля запроса",
        "details": { "fields": { "phone": "is required" } }
    }
}

Поле error.code — это enum из фиксированного набора, часть публичного контракта: переименование = breaking change. Основные коды, с которыми сталкивается интегратор:

КодHTTPКогда
MISSING_API_KEY401Нет заголовка Authorization
INVALID_API_KEY401Ключ не существует или подделан
KEY_REVOKED401Ключ отозван
INSUFFICIENT_SCOPE403У ключа нет нужного scope
API_NOT_AVAILABLE_ON_PLAN403Тариф не включает API
TRIAL_READ_ONLY403Триал — мутирующие методы запрещены
RATE_LIMIT_EXCEEDED429Превышен лимит per-minute / per-day
VALIDATION_ERROR422Ошибка валидации полей
INVALID_JSON400Некорректный JSON в теле
CONTACT_NOT_FOUND422Контакт не найден или не в проекте
CHANNEL_NOT_SUPPORTED_YET422Канал есть в системе, но не подключён к 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, модель, код ошибки и поле. Из него генерируется остальное:

  1. Правка в openapi.yaml (новый endpoint / поле / код ошибки).
  2. openapi-generator-cli с пресетом PHP → sdk-php (BotixPro\Sdk\Client).
  3. То же для Python → sdk-python (пакет botix).
  4. То же для Node → sdk-node (@botix/sdk, BotixClient).
  5. Из той же спеки рендерится Redoc — публичная документация на developers.botix.pro.
  6. Из той же спеки рендерится Swagger UI на developers.botix.pro/try-it — интерактивная песочница, где можно подставить Bearer-токен и слать реальные запросы из браузера.

Один файл — шесть артефактов, любое изменение расходится за минуту без копипасты. Отдельная боль обычных доков — код-сэмплы, которые пишут вручную и не успевают синхронизировать. В OpenAPI это решается расширением x-codeSamples — примеры лежат прямо под endpoint внутри спеки:

yaml
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-очередь; те же миграции, та же логика обработки ошибок, те же коды и заголовки в ответе. Различий ровно три:

  1. Лимиты тарифа. api_rate_per_minute для триала ниже (в диапазоне от 30 до 2000 в зависимости от тарифа), api_rate_per_day — от 1000 до 1 000 000. Механизм rate-limit тот же, что у платных, просто порог жёстче.
  2. Тогл записи. api_write_enabled=0 превращает кабинет в read-only — все POST/PUT/DELETE возвращают 403 TRIAL_READ_ONLY. Удобно для смоук-тестов чтения без риска что-то изменить; тот же тогл доступен и платным клиентам.
  3. Срок жизни. 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 для чат-интеграций.