Публичный API
поверх продукта
Практический гайд с ретроспективой одного дня сборки: как меняется модель угроз, когда веб-студию с картинками, видео и чатом открывают программным ключом и скриптом.
Зачем этот текст
Представьте готовый продукт: веб-студия, баланс в рублях, каталог ИИ-моделей, история генераций. Пользователь кликает по кнопкам в браузере, ждёт результат и скачивает файлы. В таком интерфейсе безопасность и экономика частично держатся на «физических лимитах человека»: пользователь за мышкой физически не способен отправлять сотни тяжелых запросов в секунду и «накручивать» счет провайдера.
Публичный API — это тот же каталог моделей и тот же кошелёк, но вход открыт через секретный API-ключ и стандартный HTTP-протокол. Вместо человека за экраном приходит скрипт или автоматический ИИ-агент, которому всё равно, есть ли в интерфейсе кнопка «Сгенерировать». Снаружи кажется, что мы просто «добавили пару эндпоинтов». Изнутри же кардинально меняется модель угроз: меняется всё, что можно сделать с деньгами аккаунта, чужими и своими данными, исходящими вебхуками и квотами.
Этот выпуск — не реклама функций и не сухой справочник методов API. Это практический инженерный разбор:
- почему три модели списания (резервирование, предоплата и списание по факту) вытекают из самой природы картинок, видео и чата — и почему их нельзя свести к единой схеме;
- как ломается разграничение доступа при переносе кода из веб-интерфейса (изоляция списков задач, права ключей, проверка URL вебхуков);
- какие процессы должны выдерживать деплой (гарантированная доставка уведомлений и идемпотентность вызовов);
- как проектировать документацию сразу для двух аудиторий — людей-разработчиков и ИИ-агентов;
- как устроена дисциплина ревью, при которой 8 итераций за день превращаются в накопитель требований к безопасности.
Внутри — реальные цифры одного дня сборки, наглядные схемы и сопоставление с OWASP API Security Top 10 (2023), практиками Stripe, Replicate, OpenAI, Anthropic и отчётами о безопасности сгенерированного кода.
Что унести с собой
- Сравнительную матрицу «Студия vs API» по финансам, данным, исходящим URL и квотам.
- Три потока биллинга и дерево решений (decision tree) по выбору модели списания.
- Принципы защитного блокирования (fail-closed) на платных маршрутах, устойчивые очереди вебхуков и методику тестов на непустых чужих данных.
- Пошаговый чеклист из 10 пунктов перед открытием публичного доступа.
- Понимание ревью до мержа на платных путях не как формальной процедуры, а как инструмента накопления требований.
1. Карта результата: что вообще построили
Прежде чем разбирать уязвимости и модель угроз, наметим карту созданного API-контура. Без этого разговор рискует уйти в абстракцию.
1.1. Поверхность интеграции
| Назначение | Суть |
|---|---|
| Каталог моделей | Картинки + видео + чат, с параметрами, лимитами и ценами |
| Баланс | Проверка эффективного остатка (личного кошелька или баланса организации) |
| Загрузки | Исходники и медиа-референсы для генерации |
| Чат | Совместим с распространенным индустриальным форматом (обычный ответ и stream) |
| Картинки / видео | Асинхронные задачи: создать, получить статус, список; отдельный расчет цены |
| Ключи | Выпуск, отзыв, ротация секретов вебхуков из личного кабинета |
| Открытый каталог цен | Витрина тарифов для лендинга и поисковиков, без авторизации |
Права API-ключа не должны выдаваться по принципу «всё или ничего»: отдельное право на чат, отдельное — на медиа, отдельное — на чтение баланса. Это критично: утечка ключа с правом просмотра баланса имеет совсем иной масштаб ущерба, чем компрометация ключа с правом запускать дорогие модели.
1.2. Объём дня (наши числа)
| Показатель | Значение |
|---|---|
| Итераций с ревью и выкладкой | 8 |
| Коммитов | 23 |
| Файлов изменено | 111 |
| Строк добавлено / удалено | 16 428 / 957 |
| Новых файлов | 75 |
| Кода публичного контура (бэкенд, без фронта и тестов) | ~5 200 строк |
| Тестов публичного API | 77 в 10 файлах (2 191 строка) |
| Всего зелёных тестов бэкенда к концу дня | 1 361 |
| Выкладок бэкенда | 12 |
| Стартов фронт-слотов | 16 |
| Простоев для пользователей студии | 0 (blue/green, переключение без разрыва) |
| Выданных API-ключей к концу дня | 0 |
Значение «0 выданных ключей» — это не отставание от графика, а инженерная дисциплина. Автотесты с моками внешних провайдеров пройдены, но пока мы не проверили полный цикл работы с реальным боевым кошельком (шаг «Клиент №0»), давать доступ внешним пользователям нельзя.
1.3. Зачем нужен OpenAI-совместимый контракт чата
К 2025–2026 годам контракт вида POST /v1/chat/completions с привычными полями messages, model и stream стал общепринятым индустриальным стандартом. Разработчики, библиотеки, свитчеры и шлюзы (OpenRouter, LiteLLM и их аналоги) рассчитывают именно на эту структуру.
Для разработчиков это продуктовое удобство: достаточно поменять base_url и api_key в существующем коде, чтобы переключиться на наш бэкенд без переписывания клиента.
Однако полная слепая копия опасна: если у вас собственная валюта списания (рубли), свои правила вызова инструментов или отдельные эндпоинты для генерации медиа, все различия (gaps) должны быть явно зафиксированы в документации. Иначе ИИ-агент или SDK будут ожидать стандартного поведения провайдера и получать непредсказуемые ошибки.
2. Ретроспектива дня: как собирали
2.1. Пошаговый процесс
Работа велась короткими итерациями по 30–60 минут. Каждая итерация включала цепочку: отдельная ветка → написание кода и тестов → перекрестное ревью → исправление замечаний → слияние → деплой на стенд.
| Время (порядок) | Фокус |
|---|---|
| 1 | Ключи, картинки, видео, вебхуки, вкладка в настройках |
| 2 | Весь каталог моделей, списки задач, расчёт цены |
| 3 | Чат, совместимый с распространённым форматом |
| 4 | Вызов функций и веб-поиск |
| 5 | Маршрутизация провайдеров, переименование флагманской модели |
| 6 | Человеческие ошибки, история трат, лендинг |
| 7 | Русская документация, разделение лендинга и руководства |
| 8 | Контакт поддержки, машинная версия для агентов (коды ошибок, лимиты) |
2.2. Что определило качество
Главный фактор успеха — не скорость набора строк, а обязательное ревью до мержа на каждом проходе. В 7 из 8 итераций ревью выявляло критические дефекты в логике списания денег или безопасности. К вечеру количество таких проблем снижалось не потому, что мы «стали писать аккуратнее», а потому что каждое найденное замечание мгновенно превращалось в жесткое автоматизированное правило, которому обязана была соответствовать следующая итерация.
2.3. Инфраструктурные нюансы, о которых часто молчат
- Параллельная работа агентов. Работа нескольких автономных агентов в одном рабочем каталоге без git worktree ведет к конфликтам незакоммиченных файлов. Каждая задача должна исполняться в изолированной директории и ветке.
- Частые коммиты. Накопление изменений только в рабочей копии создаёт риск потери кода. Коммитить результат нужно сразу после прохождения микро-шага.
- Контроль деплоя. Фоновый запуск деплоя «запустил и забыл» на shared-сервере опасен. Инженер обязан четко понимать, какой слот (blue или green) сейчас активен.
- Вшивание статики в билд. Скрипт сборки копирует в релизный слот скомпилированный бандл и директорию
public. Попытка в рантайме прочитать исходный markdown-файл с диска на проде упадет, так как исходников в релизном слоте нет. Документация должна вшиваться в статический HTML при сборке. - Влияние прокси. Переменные HTTP-прокси в рабочей среде могут блокировать загрузку шрифтов и ресурсов при ручной сборке. Скрипт деплоя обязан явно очищать окружение.
Вывод. Скорость написания кода ИИ-ассистентом умножает не только пользу, но и возможный ущерб. Без накопления требований ревью быстрый день превращается в быструю дыру на платном контуре.
3. Модель угроз: мышь против скрипта
3.1. Суть сдвига
Любой фрагмент логики, работавший годами в веб-студии, при переносе в публичный API подлежит полной переоценке. Главный вопрос: какие неявные допущения о поведении пользователя зашиты в существующий код?
3.2. Почему возникает проблема
В веб-студии:
- Пользователь ограничен интерфейсом (формами, кнопками, диалогами);
- Темп запросов ограничен скоростью человека;
- Запрос списка «моих задач» автоматически привязан к авторизованной сессии браузера.
В публичном API:
- Тело запроса формируется скриптом и может достигать десятков мегабайт;
- Запросы отправляются сотнями в секунду в параллельных потоках;
- Эндпоинты получения данных принимают открытые идентификаторы и фильтры;
- URL вебхука задается клиентом (сервер сам выполняет HTTP-запрос по указанному адресу);
- Новые API-ключи создаются за секунды, если не установлен лимит на их количество.
Паттерн «поверхностно проверили баланс → отправили дорогой запрос провайдеру → списали деньги как получилось» в веб-интерфейсе может жить годами. Под API-ключом с лимитом в десятки запросов в минуту такой паттерн мгновенно приводит либо к бесплатной выдаче результатов, либо к огромному счету от провайдера ИИ-моделей.
3.3. Матрица различий «Студия vs API»
| Контур | В студии | Через публичный API |
|---|---|---|
| Деньги | Человек физически не отправит гигантский контекст и сотню параллельных чатов | Параллельные задачи, гигантские payloads, stream, вызовы инструментов |
| Данные | UI показывает «моё» на основе сессии | Запросы list/get, опасные фолбэки «если пусто — покажи хоть что-то» |
| Исходящие URL | Практически не используются | Вебхуки, тестовые доставки, фетч ресурсов по внешнему URL |
| Квоты | Ограничены сессией и UI | Ключи и аккаунты; обход суточного лимита выпуском новых ключей |
3.4. Внешний язык той же мысли (OWASP API Top 10)
Классы уязвимостей согласно OWASP API Security Top 10 (2023):
- API1 — Broken Object Level Authorization (BOLA). Права на вызов эндпоинта не означают права на конкретный ID объекта.
- API4 — Unrestricted Resource Consumption. Опасность не только уронить бэкенд, но и сжечь чужие платные ресурсы (токены и GPU-время).
- API6 — Unrestricted Access to Sensitive Business Flows. Автоматизация процесса, который в UI был защищен неудобством ручного ввода.
- API7 — Server-Side Request Forgery (SSRF). Сервер делает HTTP-запрос на URL, переданный клиентом в настройках вебхука (риск атаки на внутренние сервисы и облачные метаданные).
Правило №1. При переносе внутреннего кода наружу пересматривают неявные допущения, а не только добавляют заголовок Authorization: Bearer.
4. Деньги: три модели в одном API
Это центральный инженерный раздел. Попытка свести биллинг генерации картинок, видео и чата к единой схеме неизбежно приводит к финансовым дырам.
4.1. Обзор трех моделей
Три разные схемы списания — это не непоследовательность, а прямое следствие природы операций.
| Модальность | Модель биллинга | Инженерное обоснование |
|---|---|---|
| Картинки | Резервирование при приеме → сверка по факту → финальное списание или возврат разницы | Стоимость известна заранее исходя из параметров; резервируем до обращения к провайдеру, чтобы параллельные запросы не исчерпали остаток |
| Видео | Полное списание на входе → автоматический возврат при сбое | Тариф жестко привязан к длительности/пакету; критичен гарантированный возврат при сбое провайдера |
| Чат | Списание по факту ответа, с контролируемой политикой ухода в минус | Точную длину ответа заранее не знает никто; ответ уже оплачен вами провайдеру — не списать означает подарить |
Рынок 2025–2026 годов смещается к prepaid-модели для медиа-API (у Replicate с 16 июля 2025 года prepaid обязателен для новых аккаунтов; fal.ai использует prepaid-кредиты). Для LLM-API списание токенов происходит после генерации; у OpenAI в справке по prepaid прямо описано: из‑за задержки биллинга баланс может уходить в минус и вычитаться из следующего пополнения. Отрицательный баланс — это осознанный компромисс, но он требует жесткого потолка и полного аудита.
4.2. Картинки — резервирование (Reserve & Settle)
Задача. Не позволить пяти параллельным генерациям списать один и тот же баланс пять раз «в уме», пока провайдер ещё выполняет расчет.
Механика. Оценили стоимость → зарезервировали баланс → вызвали провайдера → получили результат → списали фактическую сумму (или вернули разницу).
Аналогия. Механизм Hold & Capture в эквайринге (авторизация суммы отелем при въезде и окончательный расчет при выезде). У fal.ai в документации зафиксировано: оплата происходит за успешный output, время ожидания в очереди не тарифицируется.
Итог. Резервирование выполняется до внешнего вызова. Иначе гонка параллельных запросов (race condition) станет финансовой дырой.
4.3. Видео — списание на входе (Upfront & Refund)
Задача. Длительность и разрешение известны в момент запроса, цена прямо пропорциональна параметрам.
Механика. Полное списание средств (или жесткий pre-check «баланса хватает на всю оценку») → запуск задачи → при сбое провайдера — автоматический возврат по политике.
Ориентир. У OpenAI на странице цен видео (Sora) тарификация идет за секунду рендера. У fal.ai — за секунду работы или видео-юнит.
Итог. Видео требует принципа fail-fast по финансам на входе. Вариант «посмотрим баланс после рендера» недопустим.
4.4. Чат — списание после ответа (Post-charge)
Задача. При потоковом ответе (stream) точное количество токенов становится известно только в момент завершения генерации.
Механика. Допускается старт при минимальном допустимом остатке / оценке worst-case; финальное списание происходит по факту; ошибка списания не маскируется под успешный ответ.
Почему уход в минус бывает оправдан. Альтернатива — сбросить уже сгенерированный и оплаченный вами провайдеру ответ. Но без потолка минуса и ограничений контекста post-charge превращается в безлимитный кредит.
4.5. Составная уязвимость дня: три безобидных слоя
На итерации 3 в публичном чате пересеклись три отдельных решения:
- Списание после ответа при нехватке средств логировало ошибку, но возвращало клиенту готовый ответ.
- Предварительная проверка смотрела не на реальную цену запроса, а на фиксированный порог 5 ₽.
- Размер входа не ограничивался схемой (в студии лимит был 100 000 символов, а в API принималось тело до 20 МБ). При контекстных окнах до миллиона токенов один запрос на дорогой модели оценивался до ~550 ₽, а на самой дорогой — до ~1 100 ₽.
Результат склейки: имея 6 ₽ на балансе, клиент отправлял гигантский контекст. Проверка «баланс ≥ 5 ₽» проходила. Бэкенд платил провайдеру 1000 рублей, списание 1000 рублей падало с ошибкой, но клиент всё равно получал сгенерированный ответ! При этом отсутствие лимита на количество API-ключей позволяло повторять это бесконечно.
Как закрыли. Выделили денежный модуль чата, перешли на каноническое списание, связали предварительную проверку с реальной оценкой худшего сценария и ограничили размеры входных сообщений и выходных токенов.
Главный вывод. Опасен не сам факт списания по факту ответа, а сочетание «после» + «проверка не связана с ценой» + «вход не ограничен».
4.6. Инструменты и поиск — второй счетчик (SKU)
Если чат использует веб-поиск или вызов функций (function calling), токены ≠ полная цена.
- У OpenAI built-in tools (веб-поиск) тарифицируются в районе $10 за 1 000 вызовов плюс токены содержимого. Один поиск может порождать несколько внутренних sub-search, из-за чего счет растет кратно быстрее.
- Anthropic в 2025 году анонсировал web search API с отдельной платой за вызовы поиска (около $10 / 1k) плюс токены.
Наш случай. Первоначальный расчет учитывал только токены, а нативный поиск провайдера не тарифицировался. Клиент мог включать платные инструменты за наш счет.
Правила. Tool — это отдельный SKU в предварительной оценке и финальном списании. Встроенные тяжелые инструменты провайдеров (code interpreter) по умолчанию должны быть запрещены.
4.7. Снятые модели и «тихий тариф»
Кейс. Модель убрали из пользовательского каталога студии, но её старый ID продолжал приниматься роутером. Цена откатывалась к дефолтному тарифу провайдера — например, $3 / $15 вместо фактических $5 / $25, что давало ~40% недобора на каждом вызове.
Правило. Единый реестр моделей: id → актуальная цена → преемник при deprecation. Нельзя угадывать тариф по совпадению имени.
4.8. Дерево решений: какую модель списания выбрать
- Стоимость известна до старта с приемлемой точностью?
- Да, и операция долгая/параллельная → Reserve + Settle (картинки).
- Да, и провайдер тарифицирует юнит/секунду → Upfront / Hard pre-check (видео).
- Стоимость известна только после ответа (токены, stream)?
- → Post-charge + жесткие лимиты контекста + минимальный депозит + обработка ошибок списания + потолок минуса.
- Подключены tools (поиск/функции)?
- → Включаем второй meter (SKU) в итоговую оценку.
5. Доступ: данные, права, лимиты
5.1. Вебхук и исходящий URL (SSRF)
Задача. Пользователь указывает URL для получения уведомлений о статусе задачи. Сервер обязан выполнить HTTP-запрос наружу — и именно здесь возникает риск SSRF (Server-Side Request Forgery).
Кейс дня. Проверка URL сводилась к проверке префикса https://. Приватные сети (10.x.x.x, 192.168.x.x), link-local адреса (169.254.169.254) и loopback не отсекались. При этом в проекте уже существовали два готовых валидатора, но ни один не вызывался в контроллере вебхуков!
Как закрыли. Выделили модуль валидации URL с автотестами; включили проверку при создании и обновлении ключа; исключили подпись HMAC из журналов доставок.
5.2. Список задач: «если пусто — отдай хоть что-то» (BOLA)
Кейс. Логика эндпоинта GET /v1/jobs: отфильтровать задачи с меткой API, а если таких нет — вернуть последние задачи пользователя из веб-студии.
В результате свежий API-ключ при первом же запросе отдавал приватные промпты и изображения, созданные владельцем аккаунта через браузер.
Урок. Пустой список — это корректный ответ API. Нельзя подмешивать данные из других контуров. Автотест «вернулся пустой список» не проверяет изоляцию. Нужен тест на базе с непустыми чужими данными.
5.3. Порядок проверок и область ключа
Загрузка файлов была заявлена для ключей с видео-правами, но проверка прав срабатывала раньше тела обработчика и резала сценарий.
Правило. Порядок middleware и декораторов — это часть контракта API. Спецификация OpenAPI и реальный pipeline должны полностью совпадать.
5.4. Лимиты при недоступном Redis: fail-open vs fail-closed
Кейс. При недоступности Redis сервис ограничений (rate limiter) работал в режиме fail-open (пропускал запросы). На платных генерациях это означало: пока Redis недоступен, любой скрипт может бесплатно генерировать видео за ваш счет!
Правило. На всех платных маршрутах (money-path) используется защитное блокирование — fail-closed (отказ с кодом 503 Service Unavailable).
5.5. Каталог: не открывать всё звёздочкой
После расширения покрытия каталог API подтянул модели студии по wildcard-маске. В результате любая будущая модель студии автоматически становилась доступной через API без проверки цены.
Правило. Только явный allowlist или флаг published_to_api. Витрина студии ≠ витрина API-ключа.
6. Устойчивость и контракт с клиентом
6.0. Двух правил устойчивости
- Абсолютные URL в ответах. Все ссылки на готовые медиафайлы в JSON должны быть абсолютными (содержать полный host и схему). Относительные пути
/uploads/img.pngподходят браузеру, но ломают сторонних клиентов. - Короткие транзакции БД. Внешний HTTP-запрос к провайдеру ИИ не должен выполняться внутри открытой транзакции базы данных. Транзакция на резервирование/списание должна быть короткой до и после вызова провайдера.
6.1. Вебхуки, которые переживают деплой
Кейс. Очередь повторных попыток отправки вебхуков хранилась в оперативной памяти процесса (in-memory). При деплое новой версии бэкенда процесс перезапускался, и вся очередь неотправленных уведомлений исчезала.
Решение. События сохраняются в таблице БД, а фоновый воркер с экспоненциальной задержкой (exponential backoff) доставляет их клиенту. Клиент обязан обрабатывать дубликаты по event_id (принцип at-least-once).
6.2. Идемпотентность создания задач
Повторный запрос на создание задачи с тем же заголовком Idempotency-Key должен возвращать исходный объект задачи (job id и статус), а не выдавать ошибку 409 Conflict.
6.3. Совместимость с форматом чата — где она кончается
| Возможность | Ожидание «как у крупного» | Наше намеренное поведение |
|---|---|---|
| Chat + stream | Работает со штатными SDK | Да (base_url + key) |
| Поле стоимости | Обычно USD/tokens в usage | Расширение: рубли / своя валюта кошелька |
| Нативный web search в том же поле tools | У разных провайдеров по-разному | Явная ошибка или свой путь — не тихое «как будто поискали» |
| Built-in tools провайдера (code interpreter, file search) | Иногда «просто включить» | Запрет — чужой тариф и чужие правила |
| Картинки в messages чата | Vision-chat | Не принимаем: картинки — отдельный раздел API |
7. Документация как часть API
7.1. Два адресата, которых нельзя смешивать
До разделения существовал один длинный markdown-файл (~900 строк), который служил и справкой, и содержимым лендинга.
Разделили:
| Адресат | Форма | Содержание |
|---|---|---|
| Человек | Руководство на русском языке, ~14 разделов, оглавление | Зачем, шаги, примеры cURL/Python |
| Агент / LLM-инструменты | Машинный текст на английском (llms.txt + guide) | Коды ошибок, лимиты, заголовки, без вводных |
| Поиск и прайс-лист | SSR-лендинг с живым каталогом | Цены в статических тегах HTML |
7.2. Вшивание статики при сборке
Чтение markdown-файлов с диска в рантайме в схеме blue/green деплоя упало бы на проде, так как исходные файлы контента не копируются в релизный слот.
Решение. Страница пререндерится при сборке (~342 КБ готового HTML) и отдается сервером без обращения к диску.
8. Ревью как накопитель требований
8.1. Сводка итераций дня сборки
| Итерация | Фокус | Ключевая находка | Правило, которое закрепили |
|---|---|---|---|
| 1 | Фундамент | SSRF вебхука; fail-open лимитов; webhook в памяти | Валидатор URL; fail-closed; durable queue |
| 2 | Каталог и списки | Утечка студийной истории в list | Только API-задачи; тест на непустых |
| 3 | Чат | Составная дыра денег (5 ₽ + silent charge + без лимита входа) | Канонический billing + caps |
| 4 | Tools | Неверный формат tool results; search без тарифа | Конвертер + тесты; tool SKU |
| 5 | Маршруты | Заниженный тариф снятых id (~40%) | Registry + successor + price |
| 6 | DX | Регрессия текстов ошибок в студии | Exception hierarchy = contract |
| 7–8 | Docs | Заглушка контакта; дыры в machine-доке | Сверка с кодом; dual docs |
8.2. Почему multi-pass имеет смысл на AI-скорости
Согласно отчёту Veracode GenAI Code Security (2025/2026), порядка 45% сгенерированного ИИ-моделями кода содержит уязвимости безопасности. Восемь проходов ревью за день имели смысл именно потому, что каждое замечание превращалось в автоматизированную проверку для последующих шагов.
9. Цифры дня и честный статус «ноль ключей»
- 0 выданных ключей значит: ни один запрос под боевым ключом внешнего клиента еще не дошел до реального провайдера. Заглушки в автотестах не заменяют реальный счет.
- 0 простоев при 12 выкладках бэкенда подтверждают надежность механизма blue/green слотов.
- 77 автотестов публичного контура — необходимая база перед собачьим тестированием (dogfooding).
9.1. Шаг «Клиент №0» перед запуском
- Выпустить API-ключ на боевом стенде с реальным кошельком.
- Пройти полный пользовательский сценарий: models → balance → estimate → image job → video job → chat stream → list empty → list after create → 401/403/429 → webhook.
- Убедиться, что история трат четко разделяет траты студии и API.
- Только после этого запускать публичный доступ.
10. Чеклист: десять правил перед анонсом
1. Матрица угроз на каждый переиспользуемый путь
Зачем. UI-допущения не работают в API.
Сделать. Пройти таблицу «деньги / данные / URL / квоты» по каждому контроллеру.
Проверить. Есть письменный ответ на вопрос «что делает скрипт на 60 rpm».
2. Разделение моделей биллинга
Зачем. Единая схема списания на картинки, видео и чат ведет к убыткам.
Сделать. Явно внедрить Reserve / Upfront / Post-charge + caps.
Проверить. Предварительная оценка (estimate) и списание (charge) используют единую формулу.
3. Защита Post-charge от самообмана
Зачем. Списание после ответа без лимита входа = бесплатные генерации.
Сделать. Pre-check по реальной стоимости; лимиты контекста; обработка ошибок списания.
Проверить. Тест: при балансе чуть выше порога и стоимости запроса выше баланса бэкенд возвращает отказ до обращения к провайдеру.
4. Отдельный учет вызова инструментов (SKU)
Зачем. Вызов поиска и функций увеличивает счет от провайдера.
Сделать. Выделить вызов tools в отдельную строку тарифа (SKU).
Проверить. Estimate с поиском превышает estimate без поиска.
5. Единый реестр идентификаторов моделей
Зачем. Снятый с витрины ID с баговым тарифом дает недобор средств.
Сделать. Реестр id → price → successor.
Проверить. Прогон реальных ID из логов за прошлые периоды.
6. Защита исходящих URL вебхуков (SSRF)
Зачем. Запросы бэкенда на приватные IP и cloud metadata.
Сделать. Валидатор URL при создании/обновлении ключа; подпись HMAC.
Проверить. Автотесты на приватные диапазоны, link-local и HTTP.
7. Жесткая изоляция списков задач (BOLA)
Зачем. Фолбэк на историю студии = утечка приватных данных.
Сделать. Возвращать только объекты API-контура; пустой список — норма.
Проверить. Фикстура с непустыми студийными данными не видна по API-ключу.
8. Защитное блокирование (Fail-closed)
Зачем. При падении Redis логика fail-open раздает генерации бесплатно.
Сделать. На платных путях — строго fail-closed (код 503).
Проверить. Имитация недоступности Redis на money-path дает 503 Service Unavailable.
9. Устойчивые вебхуки и идемпотентность
Зачем. Деплой не должен терять уведомления и оплаты.
Сделать. Очередь событий в БД, воркер с повторами, обработка Idempotency-Key.
Проверить. Рестарт сервиса во время повтора доставки не теряет событие.
10. Раздельная документация и «Клиент №0»
Зачем. Без машинной доки ломаются ИИ-агенты; без dogfood ломается первый клиент.
Сделать. Сформировать docs для людей и llms.txt для агентов; пройти полный сценарий Клиента №0.
Проверить. Сквозной тест Клиента №0 пройден на реальном балансе.
Справочник кодов ошибок для интеграций
| Код / Класс | Смысл | Повторять запрос (Retry)? | Действие клиента |
|---|---|---|---|
| 401 | Неверный или отсутствующий API-ключ | Нет | Проверить заголовок Authorization |
| 403 | Недостаточно прав у ключа | Нет | Выпустить ключ с нужным scope |
| 402 / insufficient_funds | Недостаточно средств на балансе | Нет (до пополнения) | Пополнить баланс |
| 400 | Ошибка в параметрах запроса | Нет | Исправить тело запроса по схеме |
| 404 | Объект или модель не найдены | Нет | Проверить ID в каталоге |
| 409 + job_id | Повторный запрос с тем же Idempotency-Key | Нет | Использовать существующий job_id |
| 429 | Превышен лимит частоты запросов | Да (с паузой) | Замедлить отправку |
| 503 | Защитное блокирование сервиса (fail-closed) | Да (короткая пауза) | Повторить запрос позже |
| 5xx | Внутренний сбой сервера или провайдера | Осторожный retry | Использовать Idempotency-Key |
11. Копируемый промпт: аудит «студия → публичный API»
Ниже — готовая заготовка для проверки кода вашим ИИ-агентом или ревьюером.
Ты ревьюер публичного API поверх существующей веб-студии (картинки, видео, чат, баланс).
Проверь diff и соседний код по чеклисту. На каждый пункт: PASS / FAIL / N/A + файл + одно предложение почему.
1) Модель угроз: для каждого нового/изменённого handler опиши, что может скрипт на 60 rpm, чего не сделает человек в UI.
2) Биллинг: какая модель (reserve / upfront / post-charge), где estimate, где charge, нет ли копипасты формул.
3) Post-charge: pre-check связан с реальной ценой? Есть max input/output? Ошибка charge не глотается?
4) Tools: отдельный meter? Unknown built-in tools denied?
5) Model id: registry цены, deprecation, successor, нет тихого default price.
6) Webhook URL: validator на create и update, private/metadata deny, secret не в логах доставок.
7) List/get: ownership, нет fallback на студийную историю, тест с непустыми чужими/студийными данными.
8) Rate limit: paid path fail-closed при недоступности store, квота на account, cap на число ключей.
9) Webhooks: durable queue, retries переживают restart, идемпотентность create задокументирована.
10) Контракт ошибок: новые коды не ломают UI студии; docs (people + agent) сверены с кодом, не с старой md.
11) OpenAI-compat: матрица supported/rejected явная.
12) Ответы: asset URL абсолютные; внешний HTTP не внутри денежной транзакции.
13) Каталог API: allowlist/publish flag, не wildcard «всё из студии».
14) Запрещено: советовать fail-open на money-path, «покажем студийные данные если API пуст», глотать charge errors.
Формат ответа: таблица checklist, затем P0/P1 список, затем «что проверить фактом» (команда теста или сценарий), без воды.
Закрытие
Публичный API поверх готового продукта — это не просто «еще пара HTTP-маршрутов». Это создание принципиально нового контура финансов, доступа к данным и исходящих сетевых вызовов. Тот же код, который был безопасен под управлением мыши в веб-интерфейсе, под API-ключом обязан заново пройти полный аудит угроз.
Три разных модели списания — это не архитектурное усложнение, а неизбежное следствие природы картинок, видео и чата. Склейка «списание после ответа + формальная проверка 5 рублей + неограниченный вход» наносит бизнесу больший ущерб, чем любая одиночная ошибка. Список задач с логикой «если пусто — покажи хоть что-то» превращает сострадание к пользователю в прямую утечку приватных данных. А вебхук без проверки IP-адресов и фоновой очереди в БД ставит под удар и безопасность, и надежность системы.
Опыт одного дня и 8 последовательных итераций показал, как типовые уязвимости проявляются при переиспользовании существующего кода: готовые валидаторы оказываются не вызванными, проверенный биллинг случайно обходится, а тест на пустом списке дает обманчивый зеленый статус при наличии реальной утечки.
Статус «0 выданных ключей» в конце дня — это честная и профессиональная позиция. Следующий шаг — выполнение сценария «Клиент №0» на реальном кошельке и закрытие открытого бэклога. Иначе первыми тестировщиками новой архитектуры станут внешние пользователи — за ваш счет.
// Обсуждение
Можно писать анонимно. Укажите email, чтобы получать уведомления об ответах.