Принципы

.md

Как устроен СОЮЗ — граница 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 пока не гарантирован

Готовые файлы отдаются подписанной ссылкой. Сейчас не гарантируются ни минимальный срок доступности, ни срок фактического удаления: забери результат сразу после готовности, но не считай пропавшую ссылку доказательством удаления. Что означают разные ответы (истёкшая подпись, недоступный файл, временная ошибка хранилища) — в Джобах и результатах.