Кредиты и оплата

.md

1 кредит = 1 цент. Как считается цена картинок, видео и чата, когда списываются кредиты, эндпоинт остатка GET /v1/credits и стоимость каждого запроса.

Курс

1 кредит = 1 цент США ($0.01). Все цены в API и каталоге — в кредитах. Баланс пополняется и отслеживается в дашборде.

Учёт ведётся с точностью до микро-доллара (1 кредит = 10 000 µUSD): дешёвый чат-вызов стоит доли цента, и целый кредит был бы слишком грубой единицей. Все денежные поля API — целые числа в микро-USD, без округления: price, usage.cost, баланс в /v1/credits, суммы в вебхуках. Перевод в кредиты для показа человеку — деление на micro_usd_per_credit (= 10 000) из ответа /v1/credits; как округлять при показе, решает твой интерфейс. Дашборд СОЮЗа, например, округляет баланс до сотых кредита, а цены запросов в истории показывает точно.

Как считается цена

Правило одно для всего: цена = фактическая себестоимость генерации × единая наценка платформы. Отдельных прейскурантов «по ценности» нет: если параметр ничего не стоит провайдеру (например, звук у Seedance или разрешение у картинок с плоской ставкой), он ничего не стоит и тебе. На первом внутреннем профиле тарифы передаются продукту вместе с сертифицированным allow-list; /v1/models до общего gate выключен.

Модальность Из чего складывается себестоимость
Картинки плоская ставка за вызов — разрешение и качество цену не меняют
Видео посекундный тариф × длительность; тариф зависит от ступени качества (режим у Kling, разрешение у Seedance) и — только там, где звук платный, — от звука
Чат ставка за 1М входных + за 1М выходных токенов, списание по фактическому usage — с точностью до микро-доллара, дешёвый вызов стоит доли цента

До завершения работы price — это верхняя оценка: бронь по самому дорогому маршруту, который мог бы выполнить запрос. Итоговое списание после успеха никогда не превышает эту оценку, а когда апстрим сообщает фактический расход или работу выполнил более дешёвый маршрут — оказывается ниже; разница возвращается автоматически. Неудачные попытки внутри водопада не тарифицируются — их покрывает наценка.

Остаток по API

GET /v1/credits — программная проверка баланса (для мониторинга и алертов «кредиты кончаются»):

curl https://api.souz.ai/v1/credits -H "Authorization: Bearer sk-souz-..."
{
  "object": "credits",
  "balance": 123456700,
  "reserved": 840500,
  "overdraft_limit": 0,
  "unit": "micro_usd",
  "micro_usd_per_credit": 10000,
  "currency": "credits",
  "credit_value_usd": 0.01,
  "rate_limit": { "requests_per_minute": 1000 }
}

Суммы — целые микро-USD (здесь баланс 123 456 700 µUSD = 12 345.67 кредита). Для показа человеку раздели на micro_usd_per_credit и округляй как удобно твоему интерфейсу.

balance — доступно к трате, reserved — временно удержано выполняющимися джобами. При успехе из reserved уходит фактическая стоимость, а неиспользованный остаток (если он есть) возвращается в balance; при сбое деньги не списываются — резерв возвращается либо не создавался. Бонусом — действующий лимит запросов твоего ключа.

Когда списываются кредиты

  1. Резерв. При создании запроса ожидаемая стоимость уходит из balance в reserved.
  2. Успех. Из резерва списывается итоговая цена успешной генерации — себестоимость выполнившего маршрута (по фактическому расходу апстрима, где он сообщается) × наценка, — а неиспользованный остаток возвращается в balance тем же коммитом. Списание никогда не превышает показанную при создании оценку. allow_degradation работает по общему правилу: деградированный outcome дешевле, потому что дешевле его себестоимость (сейчас опубликован только Nano Banana Pro references 9–14 → первые 8). Для чата резерв — верхняя оценка, а разница по фактическим токенам возвращается.
  3. Сбой, который видно в джобе. Если джоба завершилась failed, деньги не списываются: созданный резерв возвращается тем же коммитом, что и этот статус, а при немедленном отказе (например, capability или недостаток средств) резерв вообще не создаётся. За такие генерации ты не платишь.

Чего мы не обещаем (честно, чтобы не было сюрпризов в сверке):

  • Оборванный чат-стрим. Если ответ уже начал приходить и потом упал (ошибка апстрима или разрыв соединения), запрос закрывается по фактически отданному объёму — по учтённым или оценённым токенам, а не полным возвратом. Ты платишь за то, что успел получить.
  • Падение нашего процесса. Резерв и запись джобы, как и списание и её финальный статус, идут одной транзакцией: после сбоя либо произошло и то и другое, либо ничего. Если провайдер ещё не вызывался, сверка возвращает резерв ровно один раз. Если durable-попытка уже была начата, деньги двигаются только после доказательства от апстрима: результат → списание, доказанное отсутствие работы → возврат; одного возраста джобы недостаточно. Чего мы не обещаем: мгновенности и автоматического исхода, когда доказательство получить нельзя. Для новой async-джобы принятый результат проходит durable artifact protocol: объект проверяется по контрольной сумме, а его манифест, списание и terminal status коммитятся атомарно. После рестарта воркер продолжает тот же provider task, а не создаёт новую платную работу. Старое или противоречивое состояние «списание есть, доставляемого результата нет» всё ещё замораживается для оператора: выдумывать обратную проводку или успех без файла система не будет.
  • Падение во время вызова провайдера. Перед вызовом мы durable записываем намерение отправки. После рестарта такая попытка не отправляется повторно: она переходит в разбор как «могла уйти». Для async-варианта допускается только провайдер с доказанным повторным получением того же результата по durable task id. Sync-провайдер, который возвращает только тело ответа и не даёт recovery-механизма, отсекается до резерва.
  • Небезопасный результат провайдера. Результат проходит лимиты размера, SSRF-защиту и проверку формата/полного декодирования до сохранения. Если уже оплаченные байты не проходят эту политику, система не делает fallback и не возвращает резерв по одному факту ошибки: джоба остаётся in_progress, а тот же durable task сверяется повторно. Это исключает вторую платную генерацию и не выдаёт клиенту потенциально опасный файл. Видео первого ProductionGo-среза отклоняется ещё до резерва и вызова провайдера, пока bounded video validator не сертифицирован.
  • Неподтверждённая попытка у провайдера. Если связь оборвалась уже после отправки запроса (таймаут, разрыв, 408 или любой 5xx от шлюза), мы не знаем, выполнил провайдер работу или нет — и в этот момент не делаем ничего необратимого: другого провайдера не зовём и резерв не возвращаем сразу. Запрос ждёт сверки: если апстрим подтвердит результат, джоба станет completed с обычным списанием; если апстрим докажет, что запрос не был принят или закончился без результата, джоба станет failed, а резерв вернётся ровно один раз. Если механизма проверки нет, апстрим всё ещё выполняет работу или lookup падает, джоба остаётся in_progress, а резерв удерживается: время само по себе не доказывает неуспех. Для чата это видно как 502 model_unavailable при удержанном резерве. Так мы не платим дважды за одну и ту же работу.
  • Повтор запроса после ошибки 500 в чате. Чат не поддерживает ключ идемпотентности: повтор — это новая генерация и новая оплата. Решай осознанно, автоматический ретрай тут не наш контракт. Это тем более верно после 502 с неподтверждённой попыткой: сначала посмотри историю.

Если баланса не хватает:

  • синхронный чат → сразу 402 insufficient_credits;
  • картинки и видео400 и объект джобы со status: "failed", error.code: "insufficient_credits"; в поле price при этом указана посчитанная цена запроса. Резерв не создаётся, деньги не двигаются.

Стоимость каждого запроса

  • Картинки и видео: поле price в объекте джобы (и в ответе создания, и при опросе GET /v1/jobs/{id}).
  • Чат без стриминга: поле usage.cost в ответе. В стриме оно приходит в usage-чанке, если апстрим этот чанк прислал; при обрыве/отсутствующем usage публичного cost может не быть, хотя расчёт всё равно завершается по доступным данным.
{ "usage": { "prompt_tokens": 16, "completion_tokens": 120, "cost": 224 } }

История расходов

Полная история запросов и график расходов по дням и моделям — в дашборде, раздел «История».