Принципы
Как устроен СОЮЗ — граница OpenAI-совместимости, простота по умолчанию, водопад провайдеров и честная верхняя граница цены в кредитах.
Снаружи — OpenAI-стиль (и где проходит граница совместимости)
Чат — OpenAI-совместимый: POST /v1/chat/completions принимает и возвращает стандартные
структуры, включая стриминг, так что код под OpenAI SDK работает после смены base_url и
api_key.
Картинки и видео — асинхронные и это наше расширение. Запрос возвращает объект джобы
(202 + id, потом опрос или вебхук), а не готовый результат в теле, как у синхронного OpenAI
Images. Совпадают аутентификация, стиль JSON и подход к именованию полей — но не форма ответа.
Так же и с ошибками: часть из них приходит объектом джобы, а не коротким конвертом
(см. Ошибки). Планируй интеграцию под async-модель, а не рассчитывай, что любой
OpenAI-клиент заработает без правок на всех эндпоинтах.
Базовые поля мы переводим в wire-format нужного провайдера сами. Но advanced-слой пока переходный:
в нём ещё видны отдельные provider-native имена (например generationType).
Provider-native reference-поля imageUrls, reference_*_urls, first_frame_url /
last_frame_url и kling_elements не являются API-контрактом и отклоняются до job/reserve.
Единый нормализатор souz.parameters/v1 уже работает до claim/job/reserve: типизирует текущий
срез, сводит sound/generate_audio к audio.generate, ловит alias-конфликты и неизвестные
поля. Canonical audio принимает только boolean, восемь старых форм доступны лишь legacy aliases;
списки референсов ограничены 14 до обхода/хеширования. Для консервативной
forward/version-skew совместимости внутри job аудио временно хранится в legacy execution
spelling, но наружу и в fingerprint оно не просачивается. Это не разрешает runtime rollback
на worker N−1: барьер AGENTS.md/ARCHITECTURE.md и ворота RSA-117 остаются обязательными.
Семейный mapping остаётся в RSA-99/RSA-100/RSA-108, поэтому mode и
generationType пока явно считаются v0 compatibility-полями, а не каноническими смыслами.
Legacy-поля не переименовываются вслепую: mode: "4K" обязан
сохранить и resolution-смысл, а video image/images станет first/last frames либо generic
references только по доказанной operation; неоднозначный запрос будет отклонён до job/reserve.
В parameters.requested/effective сами reference sources возвращаться не будут: только
ограниченные descriptors без raw/data/signed URL, digest или внутреннего locator. Деградация
останется opt-in, а максимальная цена всех разрешённых исходов резервируется до submit — списать
больше резерва нельзя. Нетерминальный job покажет эту верхнюю границу в price, terminal success
— фактически списанную цену выбранного effective outcome, не выше границы.
Просто по умолчанию — продвинутые возможности по желанию
Базовый запрос максимально простой: model + prompt, и всё работает с разумными настройками.
Если нужен текущий опубликованный набор продвинутых возможностей — параметры (negative_prompt, cfg_scale,
sound, mode, …) кладутся прямо верхним уровнем в тот же JSON. Они проверяются по белому
frozen-профилю модели: неизвестное поле или неверный type/enum/range/condition → короткий
invalid_request без claim/job/reserve. Вложенной формы params: {...} нет. Какие поля у
какой модели — в каталоге моделей; /v1/models на первом внутреннем профиле
выключен до сертифицированного allow-list.
Per-variant resolver проверяет все объявленные поля, значения, ограничения и обязательный
result artifact до резерва: несовместимый вариант в строгий водопад не попадает. Если богатый
вариант исчерпан, strict runtime не отправляет запрос бедному варианту молча: возвращает
provider-opaque capability_mismatch с опубликованными choices. Явный allow_degradation:true
разрешает только эти choices; сейчас это Nano Banana Pro references 9–14 → первые 8.
Паритет с официальными upstream-схемами тоже пока неполный: некоторые операции и поля наших
апстримов не описаны манифестами; опубликованный v0-срез уже типизирован, но не становится от
этого provider-independent mapping.
Инвентаризация request + response и lossless mapping по семействам — RSA-99 (Kling),
RSA-100 (Seedance), RSA-108 (Veo); общий per-variant resolver закрыт RSA-97.
Простой путь никогда не усложняется из-за продвинутого.
Надёжность: водопад провайдеров
У каждой модели может быть несколько провайдеров. Если первый недоступен или вернул сбой — запрос автоматически уходит к следующему. В strict mode semantic outcome, цена и формат ответа при такой скрытой смене не меняются. Lossy outcome возможен только по отдельному явному opt-in.
В strict mode цена фиксирована semantic outcome модели и запроса: скрытое переключение
провайдеров при том же outcome меняет нашу маржу, а не твой счёт. Только явный
allow_degradation разрешает заранее опубликованные outcomes с разными ценами: до submit ты
увидишь и зарезервируешь maximum charge, а итоговый price будет ценой выбранного effective
outcome и никогда не превысит эту границу. Сейчас опубликован только Nano Banana Pro
references 9–14 → первые 8; video/family degradations закрыты.
Кого именно мы выбрали, сколько это нам стоило и какая была попытка — не наше публичное поле: ни в успешном ответе, ни в ошибке. Это относится и к чату: текст отказа собираем мы сами по нормализованному коду, ответ апстрима наружу не пересылается. Раньше здесь была честно названная дыра — в чате сообщение провайдера доходило до клиента дословно; она закрыта.
Строить логику всё равно следует на error.code, а не на message: код — это контракт,
а текст мы вправе переформулировать.
Деньги: честно и прозрачно
- 1 кредит = 1 цент США ($0.01). Все цены — в кредитах.
- Цена видна заранее (каталог) и по факту: поле
priceу джобы,usage.costв ответе чата. - Резерв создаётся только для принятого запроса; при успехе списывается фактическая стоимость,
неиспользованный остаток возвращается. Если джоба завершилась
failed, деньги не списаны: резерв возвращён либо не создавался. Движение денег и статус джобы меняются одной транзакцией, поэтому падение процесса не оставляет «полусписанного» состояния. Границы этого правила (оборванный чат-стрим, время до срабатывания сверки) описаны в Кредитах — обещать мгновенный возврат в любой ситуации было бы неправдой. - Баланс и история — на dash.souz.ai.
Асинхронность для тяжёлых задач
Картинки и видео создаются асинхронно: сразу получаешь job_id, результат — опросом GET /v1/jobs/{id} или вебхуком. Это единственная схема, которая честно масштабируется и не рвёт соединения на минутных генерациях. Чат — синхронный (и со стримингом).
Результаты — по ссылке; retention пока не гарантирован
Готовые файлы отдаются подписанной ссылкой. Сейчас не гарантируются ни минимальный срок доступности, ни срок фактического удаления: забери результат сразу после готовности, но не считай пропавшую ссылку доказательством удаления. Что означают разные ответы (истёкшая подпись, недоступный файл, временная ошибка хранилища) — в Джобах и результатах.