Изменения API¶
Здесь только то, что видно клиенту: поля запросов и ответов, коды, лимиты, заметное поведение. Внутренние правки сюда не попадают. Машинный контракт всегда актуален на странице API Reference, эта страница отвечает на другой вопрос: что изменилось с прошлого раза и что с этим делать в приложении.
Ломающие изменения помечены ⚠️.
2026-10-18 - петля сетки всегда пережата¶
preview.animationбольше не бывает тем же файлом, чтоpreview.video: это всегда петля 480 по короткой стороне, по умолчанию весь клип в своём темпе. Поля и их форма не меняются, в сетке просто играет файл в несколько раз легче. Подробнее.
2026-10-15 - отзыв входа через Apple¶
POST /v1/auth/appleпринимает необязательноеauthorization_codeиз того же ответа Sign in with Apple, что иid_token. Передавай его при каждом входе: при удалении аккаунта сервер отзывает по нему доступ приложения к Apple ID, как требует App Store. Без него вход работает как раньше. Подробнее.
2026-10-14 - фото по персонажам¶
Эффект говорит, сколько людей он берёт и сколько фото каждого, а генерация принимает фото по людям. Сейчас все эффекты каталога берут по одному фото на человека, и старые сборки с ними работают как раньше.
- У эффекта новое поле
characters: список персонажей, у каждогоmin_photosиmax_photos. Первый персонаж обязателен, у необязательногоmin_photos = 0. Несколько фото одного персонажа - ракурсы одного лица.min_inputs/max_inputsостаются и равны суммам по персонажам. POST /v1/generationsпринимает фото полемcharacters:[[upload_id, …], …], по списку на персонажа в порядке эффекта. Староеupload_id+additional_uploadsтоже принимается, аadditional_uploadsтеперь до 15 элементов вместо 8.- ⚠️ Плоский список на эффект, где из него не понять, чьё фото чьё (у персонажа может быть
разное число фото), отклоняется
422с новым кодомcharacters_required. Такой эффект запускается только полемcharacters. В каталоге таких эффектов пока нет, они появятся с видео-моделями, которые берут несколько ракурсов одного человека. - Одно и то же
upload_idдважды в полеcharactersили в двух полях-фото студии -422 validation_error: каждое фото - своя загрузка. Старый плоский список повтор по-прежнему принимает. - Видео-эффекты на Grok теперь учитывают в цене входные картинки, как модель и списывает:
costтаких эффектов немного вырастет после пересохранения.
Подробно - Каталог, Генерации, Ошибки.
2026-10-12 - поиск по каталогу находит эффекты по теме¶
Контракт GET /v1/effects?search= не меняется. На сервере появился поиск по смыслу: запрос
находит эффект и без общих слов («шпион» → «Секретный агент»). Он включается на каждом
окружении отдельно и по умолчанию выключен.
- Эффекты, найденные по теме, стоят после тех, что запрос называет прямо, их не больше десяти.
- По теме находится только то, что заметно ближе к запросу, чем остальной каталог. Запрос, которому ничего не подходит, по-прежнему возвращает пустой список.
- Заметной задержки к ответу поиск по теме не добавляет.
- Запрос короче трёх символов ищется только по словам.
Подробно - Каталог.
2026-10-12 - поиск по каталогу ищет слова, а не подстроку¶
Контракт GET /v1/effects?search= не меняется: те же параметры, тот же ответ. Меняется то,
что находится и в каком порядке.
- Раньше
searchискал подстроку в названии и slug. Теперь он ищет слова запроса в названии, тегах, категориях и описании, понимает формы слова («супергероя» → «Супергерой»), опечатку в одну-две буквы и недописанное слово. Кусок из середины слова больше не находит: «герой» не даст «Супергерой» по названию. - Выдача с
searchупорядочена по близости к запросу: название, затем теги и категории, затем описание. Ранжирование внутри категории при поиске не действует. - Строку можно слать как есть, в том числе распознанную с голоса: регистр, пунктуацию и слова просьбы («покажи», «найди мне») сервер убирает.
- Потолок длины
searchподнят со 100 до 300 символов, длиннее по-прежнему422. Расшифровку длиннее 300 символов приложение обрезает по границе слова.
Подробно - Каталог.
2026-10-12 - эффект лежит в нескольких категориях¶
Контракт только расширяется, старые сборки продолжают работать.
- У эффекта новое поле
category_ids: все его категории списком.category_idостаётся и равен первой из них, включённые категории идут в списке первыми. В новом коде читай список. GET /v1/effects?category=находит эффект по любой из его категорий, поidилиslug. В плоском списке безcategoryэффект по-прежнему приходит один раз.effect_countвGET /v1/categoriesсчитает эффект в каждой его категории, поэтому сумма по категориям может быть большеtotalплоского списка.- В списки попадает только эффект, у которого есть хотя бы одна включённая категория. Раньше
эффект совсем без категории оставался в плоском списке, в поиске и на странице автора,
теперь его там нет. По прямому
idилиslugон открывается и запускается как раньше. - Порядок внутри категории учитывает ручную расстановку оператора: часть эффектов он может поставить наверх или вниз ленты. Формат ответа не меняется, список по-прежнему нужно показывать в том порядке, в котором он пришёл.
Подробно - Каталог.
2026-10-12 - голосовой ввод: распознавание речи¶
Новая ручка POST /v1/speech/transcriptions: приложение шлёт короткую запись голоса файлом
(multipart/form-data, поле audio), сервер отвечает её текстом:
- Ручка общая для любого голосового ввода. Найти эффект голосом - это два запроса:
распознать, затем передать текст в обычный
GET /v1/effects?search=. textприходит как есть, с пунктуацией модели и без ограничения длины.- Нужен токен зарегистрированного пользователя: гостю
403 registration_required. - Запись: m4a/mp4, mp3, wav или webm, не длиннее 15 секунд и не больше 512 КиБ.
- Речь не распознана - ответ
200с пустымtext. - Новый код ошибки
415 unsupported_media_type: формат записи не поддерживается. Запись сверх предела -413 payload_too_large, распознавание недоступно -503 provider_unavailable, слишком частые запросы -429 rate_limitedсRetry-After.
Существующие ручки не меняются. Подробно - Голосовой ввод.
2026-10-12 - цена своего эффекта известна сразу¶
cost у своего эффекта приходит настоящим с момента подачи заявки и нулём не бывает, у
новой заявки и у заявки-правки одинаково. Пока идёт processing, цена может вырасти: если
на референсе несколько людей, эффект считается на фото каждого из них. Проверка cost > 0
в приложении больше не нужна: она всегда истинна и готовность эффекта не показывает.
Отказ 422 (validation_error) на POST /v1/generations в первые секунды-минуты
processing остаётся: пока система не собрала эффект, он не запускается и ничего не
списывает. Подробно - Свои эффекты, «Прогнать свой эффект до публикации».
2026-10-11 - реферальный бонус не платится за свой второй аккаунт¶
Контракт не меняется. Если приглашённый и пригласивший когда-либо входили с одного
устройства (device_id в регистрации и входе), регистрация по коду проходит и привязка
пишется, но бонус не получает ни одна сторона: в GET /v1/referral растёт invited, а
credited и earned остаются прежними. Подробно - Реферальная программа.
2026-10-10 - свой шаблон без цены не запускается¶
POST /v1/generations по собственному шаблону, у которого ещё нет цены (cost: 0 в
первые секунды-минуты processing), теперь отвечает 422 вместо бесплатного запуска.
Как только система назначила цену, запуск работает как раньше и списывает её. В
приложении кнопку «попробовать» стоит показывать при cost > 0.
2026-10-10 - доля автора приходит в кошелёк после холда¶
Новые поля, старые не менялись. Меняется момент зачисления.
- Доля автора за чужую генерацию сначала ждёт холд (сейчас 14 дней) и только потом попадает в кошелёк. Если покупатель за это время вернёт деньги, все его ожидающие доли снимаются и в кошелёк не приходят.
pendingвGET /v1/user-effects/account: частьearned, которая ещё на холде.settles_atу строкGET /v1/user-effects/earnings: когда начисление придёт в кошелёк,null- уже пришло или снято.effect_earnedв истории кредитов и push о начислении приходят в момент зачисления, а не генерации.earnedв счёте и у эффекта растёт сразу, как и раньше.- Что стоит сделать в приложении: показать
pendingкак «ожидает зачисления» рядом с балансом автора, чтобы ростearnedбез роста кошелька не выглядел ошибкой.
Подробно - Свои эффекты, «Кабинет автора».
2026-10-10 - новый код отказа original_unavailable¶
Правка эффекта больше не публикуется, если, пока она ждала проверки, сам эффект ушёл из
каталога: его отклонили, сняли или он ушёл в архив. Такая правка получает rejected с
reject_reason: "original_unavailable" и готовым reject_reason_text. Паузу после отказа
код не включает. Сборки, которые показывают reject_reason_text, ничего менять не должны;
если приложение ветвится по коду, неизвестный код стоит вести как other.
2026-10-10 - login закрывается для новых устройств, если пароль подбирают¶
Полей и кодов не прибавилось. Если к одному аккаунту за час пришло много неверных паролей с
разных адресов, POST /v1/auth/login до конца часа принимает пароль только с устройства, с
которого в этот аккаунт уже входили, остальным отвечает 429 rate_limited.
- Что стоит сделать в приложении: передавать в
loginтот жеdevice_id, что и в остальных запросах. Сборка без него в такой момент войти не сможет.
Подробно - Аутентификация, «Лимиты».
2026-10-10 - push-токен привязан к установке: device_id в POST /v1/devices¶
Новое необязательное поле. Сборки, которые его не шлют, работают как раньше.
device_idв телеPOST /v1/devices- тот же идентификатор установки, что уходит при входе. Токен, зарегистрированный с ним, переходит на другой аккаунт только с той же установки: запрос с другимdevice_idили без него получает403(forbidden), а токен остаётся у прежнего владельца.- Что стоит сделать в приложении: слать
device_idпри каждой регистрации токена. Без него токен можно перехватить, зная его значение.
Подробно - Push-устройства.
2026-10-09 - повтор запроса без второго списания: Idempotency-Key¶
Новый необязательный заголовок. Сборки, которые его не шлют, работают как раньше.
Idempotency-KeyпринимаютPOST /v1/generations,POST /v1/studio/generations,POST /v1/paymentsиPOST /v1/subscriptions. Повтор с тем же ключом и тем же телом возвращает объект, созданный в первый раз: ту же генерацию без второго списания, тот же платёж с той жеpayment_url, ту же подписку вместо409 already_subscribed.- Ключ создаёт приложение, один на нажатие: от 16 до 64 символов из
A-Z a-z 0-9 _ -, удобнее всего UUID. Автоматические повторы одного нажатия идут с тем же ключом. - Новые коды ошибок, оба
409:idempotency_key_reused(ключ уже приходил с другим телом, это ошибка в приложении) иrequest_in_progress(только оплата и подписка: первый запрос ещё ждёт ответа платёжного центра, повторить через 1-2 секунды с тем же ключом). Если общий обработчик409у тебя показывает «уже подписан», разведи его поcode. - Что стоит сделать в приложении: слать ключ на всех четырёх ручках и после обрыва связи или таймаута повторять запрос с ним же, а не начинать заново.
Подробно - Ошибки, «Повтор запроса», примеры в Генерации и Оплате.
2026-10-09 - у генерации в processing появился срок¶
- Запуск, который провайдер не закончил за час с отправки, завершается
failedсerror_code = provider_timeout, кредиты возвращаются. Раньше такой запуск мог оставаться вprocessingбез срока. Код ошибки прежний, приложению менять нечего.
2026-10-08 - у offset в списках появилась верхняя граница¶
- ⚠️
offsetпринимает значения от 0 до 10000 во всех постраничных списках:/v1/effects,/v1/authors/{author_id},/v1/generations,/v1/credits/transactions,/v1/user-effects,/v1/user-effects/earnings. Больше -422 validation_error. Раньше граница была только уlimit. totalпо-прежнему считает все строки, а получить можно первые10000 + limit. Листание заканчивай наoffset = 10000, а не поtotal. Пока списки короче, приложению менять нечего.
2026-10-05 - точка фокуса постера и клипа¶
Новые поля, старые сборки их не читают и работают как раньше.
preview.focusу эффекта:{"x": 0.63, "y": 0.34}, точка постера в долях ширины и высоты от левого верхнего угла. Когда плитка другой формы, чем постер (карточка 3:4, квадратное промо), окно обрезки ставится центром на эту точку, а не на середину кадра, и лицо остаётся в плитке.null- точки нет, режь по центру.preview.video_focus- то же для клипа, одна точка наpreview.videoиpreview.animation. Отдельная от постера: клип часто другой формы.
Формула выравнивания - Каталог, поле preview.
2026-10-05 - подписка: кредиты плана сгорают, отмена с причиной¶
Раздела подписок в приложении ещё нет, поэтому сломать нечего, но экран делать уже по этому контракту.
- Кредиты плана живут один период. При продлении остаток сгорает и приходят кредиты нового периода; когда подписка закрывается, остаток сгорает совсем. Пакеты, приветственные и заработанные кредиты не сгорают. Раньше не сгорало ничего.
GET /v1/credits/balanceотдаёт два новых поля:plan_credits(какая частьbalanceсгорит) иplan_expires_at(когда;nullбез подписки).balanceзначит то же, что и раньше. В истории появилсяkind: "plan_expired"- запись о сгорании.- ⚠️
POST /v1/subscriptions/{id}/cancelтребует тело:reasonиз закрытого списка и необязательныйcomment. Безreason-422. - Один план на аккаунт. Другой план оформляется без отмены текущего и заменяет его в момент оплаты, остаток прежнего сгорает.
- Напоминания push за 3 дня и за 1 день до конца периода:
data.status="period_ending",data.days_left="3"или"1".
Подробно - Подписки и Кредиты.
2026-10-04 - студия: цена на каждый запуск, потолок длины ответа¶
Поля и формат ответа прежние.
- Цена считается на каждый запуск. Раньше её фиксировал первый запуск с данными ответами:
запуск с двумя фото после запуска с одним списывал цену одного, а в обратном порядке
expected_costотвечал422«Цена изменилась» при той же оценке. Теперь списание всегда равно оценке для приложенного числа фото. - Текстовый ответ формы - до 1 000 символов, запрос после подстановки ответов - до 10 000.
Длиннее -
422 validation_error. Ограничь поле ввода в приложении.
Подробно - Студия.
2026-10-04 - подписка: только карта, условия фиксируются при оформлении¶
Формат ответов прежний, меняются ответы на оформление (POST /v1/subscriptions):
method: "sbp"теперь отвечает422 validation_error. Раньше такая подписка оформлялась, отрабатывала первый период и молча не продлевалась: продление идёт по токену карты.- Появились
403 forbidden(план продаётся ограниченное число раз на аккаунт) и429 rate_limited(слишком много незавершённых оплат), те же, что у разового пакета.
Продление списывает сумму и начисляет кредиты, зафиксированные при оформлении (amount_minor
и credits подписки), а не текущую цену плана. Подписка, которую нечем продлить, переходит в
expired, когда кончается оплаченный период. Подробно - Подписки.
2026-10-04 - failed до отправки модели приходит в стрим и пушем¶
Поля и формат ответа прежние. Если генерация падала ещё до отправки модели (негодное фото,
отказ провайдера принять задачу), кредиты возвращались, но ни события в SSE-стриме, ни пуша не
было: стрим молчал до конца своего срока жизни, и узнать об ошибке можно было только запросом
GET /v1/generations/{id}. Теперь терминальный статус приходит в стрим и пушем при любом
исходе. В приложении менять ничего не нужно. Подробно - Генерация.
2026-09-30 - расширение результата по формату¶
Поля и формат ответа прежние. Картинка раньше лежала под .jpg, даже когда внутри PNG или
WebP, и приложение, которое сохраняло файл по имени из ссылки, получало PNG с расширением
.jpg. Теперь расширение в result_url то же, что у файла, который отдала модель, и
совпадает с форматом. Результаты, готовые до этого дня, остаются под .jpg, так что формат
по-прежнему надёжнее брать из Content-Type. Подробно - Генерация.
2026-09-29 - петля сетки выбирается по эффекту¶
Поля и формат ответа прежние. preview.animation раньше всегда была первыми 3 секундами
клипа, и у превью «до/после» в сетку не попадало «после». Теперь контент выбирает петлю для
каждого эффекта: первые 3 секунды, весь клип за 3 секунды, весь клип в своём темпе или сам
клип карточки. Петля может быть длиннее 3 секунд и совпадать с preview.video. Играй её
по-прежнему без звука и по кругу, на длину не рассчитывай. Подробно - Каталог.
2026-09-28 - свой промпт в студии¶
Новые необязательные поля, старые не меняются: сборка, которая их не читает и не шлёт, работает как работала.
prompt в категории студии (GET /v1/studio/categories) - промпт задачи, если она
разрешает его править, иначе null. Показывай его заполненным в поле ввода.
prompt при запуске (POST /v1/studio/generations) - текст пользователя вместо промпта
задачи, до 10 000 символов. Пустой или не присланный - запуск по промпту задачи. Подстановки
полей формы ({product_name}) в нём работают. В задачу с prompt: null его присылать нельзя,
придёт 422. Цена от текста не зависит. Подробно - Студия.
2026-09-28 - результат с прозрачным фоном¶
Поля и формат ответа прежние. Эффект вроде «убрать фон» теперь возвращает PNG с
альфа-каналом, а не картинку на подложке. Показывай его без подложки, «Сохранить» и
«Поделиться» отдавай тем же PNG, иначе фон закрасится. Формат бери из Content-Type ответа:
расширение в result_url у картинки всегда .jpg. Превью для галереи (preview.thumb,
WebP) прозрачность тоже сохраняет. Подробно - Генерация.
2026-09-24 - клип и петля не больше 60 fps¶
Поля и формат ответа прежние. Клип (preview_video_url, он же preview.video) и петля для
сетки (preview.animation) теперь не больше 60 кадров в секунду: телефонный экспорт в 120 или
240 fps перекодируется в 60, ролик в 60 fps и медленнее сохраняет свою частоту. Пропорция
по-прежнему как у загруженного ролика.
2026-09-23 - превью результатов для галереи¶
Новое необязательное поле, старые не меняются: сборка, которая его не читает, работает как работала.
preview в генерации - уменьшенная копия результата для галереи и истории: WebP 480 по
короткой стороне (thumb), у видео ещё poster (кадр на полсекунды от начала, тот же файл),
размер оригинала (width, height) и blurhash. Приходит в GET /v1/generations и
GET /v1/generations/{id}. Копия весит десятки килобайт вместо мегабайт у result_url, а у
видео наконец есть кадр для плитки.
Ссылки подписанные и живут столько же, сколько result_url: результаты личные. preview =
null у старых генераций и пока генерация не готова: тогда показывай result_url, как
раньше. Копия готова до succeeded, так что деталь, запрошенная по SSE или push, уже её несёт.
Подробно - Генерация.
2026-09-22 - теги для фильтров каталога¶
Новый эндпоинт, существующие не меняются.
GET /v1/tags - чипы фильтра: slug, name на языке locale и effect_count. slug
уходит в GET /v1/effects?tag=<slug>, name показывается пользователю: в tags эффекта
лежат ключи латиницей, а не подписи. В ответе только теги, которые стоят хотя бы на одном
доступном эффекте, самые частые первыми. Подробно - Каталог.
2026-09-22 - превью каталога в размер тайла¶
Новое необязательное поле, старые не меняются: сборка, которая его не читает, работает как работала.
preview в эффекте - постер в трёх размерах WebP (thumb / card / full, по
короткой стороне 480 / 720 / 1080), размер оригинала (width, height) и blurhash для
заглушки. Приходит везде, где приходит эффект: GET /v1/effects, GET /v1/effects/{id},
профиль автора, а также в элементах GET /v1/user-effects. Оригинал в preview_url весит
мегабайты, копия для сетки - десятки килобайт. URL неизменяемые, кэшировать можно навсегда.
preview = null, пока копии не готовы: тогда показывай preview_url, как раньше.
Подробно - Каталог.
Петля для сетки - видео, а не WebP. preview.animation - MP4 480 на 3 секунды без
звука, около 200 КБ: анимированный WebP того же размера весил 0,5-1 МБ. Играть без звука и
по кругу. preview_animation_url для старых сборок остаётся WebP.
Клип превью легче. preview_video_url теперь H.264 720 по короткой стороне, без звука,
с индексом в начале файла: плеер стартует до полной докачки. Адрес у каждого эффекта
сменится один раз, поле и формат ответа прежние.
result_url живёт час вместо 10 минут. Галерея результатов больше не сереет посреди
сессии. Истёкшая ссылка по-прежнему лечится повторным запросом генерации.
2026-09-21 - студия: генерация под свою задачу¶
Новые ручки, ничего существующего не меняют: клиент, который их не вызывает, продолжает работать как работал. Но это первое место, где при генерации передаются не только фотографии - ответы формы и выбранные настройки, - поэтому смотреть на неё как на «ещё один эндпоинт каталога» не стоит. Полное описание - Студия.
GET /v1/studio/categories - список задач («убрать фон», позже другие) с описанием
формы: какие поля спросить, какие обязательны, какие настройки модели эта задача разрешает
менять. Экран строится из этого ответа: состав задач и форм меняется в админке без
релиза приложения. Набор типов полей (photo, text, enum, integer, boolean)
фиксирован - новый тип потребует релиза, и мы предупредим заранее.
POST /v1/studio/generations - запуск. Ответы формы идут в values (ключи - это
key из fields), выбранные настройки в params; поле photo несёт идентификатор
обычной загрузки. Промпт собирается на сервере по шаблону задачи. Ответ такой же, как у
обычной генерации - 202 {id, status, cost}, дальше статус, SSE и push без изменений.
POST /v1/studio/generations/estimate - цена в кредитах до запуска, считается тем же
движком, что и списание. Зависит и от настроек, и от числа фотографий (photos).
expected_cost в запуске (необязательный) - цена, которую вы показали пользователю.
Если к моменту запуска она выросла, придёт 422 с новой ценой и без списания; если
упала - запуск идёт по меньшей. Не прислали - спишется актуальная.
Результат студийного запуска приватный: он не появляется в каталоге и виден только автору, в обычной истории генераций. Публикация в каталог - по-прежнему заявкой автора.
Гостю студию видно, но запускать нельзя. GET /v1/studio/categories и оценка отвечают и
гостевому аккаунту - экран студии можно показать целиком до регистрации, вместе с ценой.
POST /v1/studio/generations гостю отвечает 403 registration_required: ведите на
регистрацию, обновлять токен бесполезно. После регистрации аккаунт тот же, заполненная форма
и кредиты остаются.
Чего пока нет: задачи, которой не нужна ни одна фотография («создать картинку с нуля по
описанию»). Путь генерации стартует от загрузки пользователя, и такая форма ответит 422.
Планируете такой экран - напишите нам заранее.
2026-09-21 - автор сам убирает свой шаблон из каталога¶
Отменяет ограничение из записи ниже: там снимать опубликованный шаблон мог только оператор. Ломающих изменений нет, ручка новая.
POST /v1/user-effects/{id}/unpublish - зеркало …/publish. Переводит свой
опубликованный шаблон в private: из каталога он пропадает, у автора остаётся - тот им
генерирует и может отправить на публикацию снова. Уже идущие генерации не прерываются.
422- шаблон и так не в каталоге; кнопку показывайте только наstatus: "approved".404- чужая заявка, как и у остальных авторских действий.
Почему передумали: «карточкой уже пользуются люди» верно для эффекта, который завели мы, и неверно для авторского. Человек сделал работу - ему и решать, висеть ей в витрине или нет; в любом маркетплейсе продавец снимает свой товар сам.
Что осталось за оператором: снятие ЧУЖОГО шаблона - по жалобе и у кураторских эффектов, где автора нет. Различить можно по push'у: своё снятие приходит только стримом, операторское - ещё и push'ом «Эффект убран из каталога».
Подробности: Свои эффекты.
2026-09-21 - свой шаблон можно оставить себе¶
Новое поле запроса необязательно, и POST /v1/user-effects без него ведёт себя ровно как
раньше - заявка уходит в модерацию. Но в поле status появилось новое ЗНАЧЕНИЕ, и оно
терминальное. Работа тут есть у всех, а не только у тех, кто возьмёт новое поле: новый
статус прилетит и клиенту, который ничего не менял, - достаточно, чтобы оператор снял с
витрины уже опубликованный шаблон его автора.
publish в POST /v1/user-effects (по умолчанию true). Передайте false, если
человек делает шаблон для себя: система так же соберёт и оттестирует его, но на модерацию
он не уйдёт. В каталоге такого шаблона нет - ни списком, ни по прямой ссылке, GET
/v1/effects/{id} отдаёт 404 даже автору, - а сам автор им генерирует как обычно.
Новый статус private - шаблон вне каталога, у автора. Приходит в двух разных
ситуациях, и это важно различать:
- собран и оставлен себе (заявка с
publish: false) - только стримом, push'а нет: это конец сборки, а не решение по заявке; - оператор вернул опубликованный шаблон автору - и стримом, и push'ом «Эффект убран
из каталога». Этот случай приходит без участия клиента: он мог никогда не присылать
publish: false.
Что сделать в приложении: обработать private как терминальный и закрывать экран
ожидания по событию стрима, а не по приходу push'а - в первом случае push'а не будет
вовсе, и экран, который его ждёт, зависнет навсегда (предупреждение целиком - в Свои
эффекты). Приватные шаблоны не занимают max_pending:
pending_now в /config считает только то, что реально ждёт модератора.
Новое поле visibility рядом со status - private, moderation или published.
Отвечает на вопрос «кому эффект виден», без вердикта модерации. Полезное следствие:
отклонённая заявка приходит как status: "rejected", visibility: "private", и запускать
её всё равно нельзя - гейтите кнопку «попробовать» по status, а не по visibility.
POST /v1/user-effects/{id}/publish - отправить свой приватный шаблон в каталог тем же
путём, что и обычную заявку. Кнопку показывайте только на status: "private": на
строящемся, уже отправленном, опубликованном или отклонённом ответ 422.
Обратной ручки в этой версии нет: снять опубликованный эффект может только оператор.
Это решение отменено в тот же день - см. запись «автор сам убирает свой шаблон из
каталога» выше. Автору по-прежнему доступен DELETE (архив).
Подробности: Свои эффекты.
2026-09-21 - скидочное предложение после отказа от paywall¶
Новый раздел /v1/offers. Ломающих изменений нет, но одно изменение поведения витрины
описано в конце записи.
POST /v1/offers/paywall-dismissed - клиент сообщает, что пользователь закрыл
основной paywall. В ответ приходит предложение со скидкой или null (предложения нет -
показывайте обычный paywall, это не ошибка):
{ "id": "…", "code": "first_paywall_discount", "expires_in": 600, "is_dismissed": false,
"product": { "id": "…", "title": "30 дней", "price_minor": 29900, "credits": 1000 } }
Окно считает сервер. expires_in приходит числом секунд, а не временем истечения, так
что часы устройства ни на что не влияют. Повторный вызов при живом предложении возвращает
то же самое предложение с уже уменьшившимся остатком: перезапуск приложения окно не
продлевает.
GET /v1/offers/current - то же предложение без побочных эффектов, для баннера и
возврата на экран. POST /v1/offers/{id}/dismiss отмечает, что экран закрыт, и
отвечает 204; скидка при этом НЕ сгорает - до конца окна она остаётся в /current с
is_dismissed: true и её по-прежнему можно оплатить.
Оплата - обычным POST /v1/payments с product_id из product.id, отдельной ручки
нет. После истечения окна (или если предложение уже оплачено, или не выдавалось этому
пользователю) тот же запрос отвечает 403 forbidden.
Гостям предложения не выдаются: обе ручки отвечают 403 registration_required.
Изменение поведения: товары, которые продаются только по предложению, больше не
попадают в GET /v1/payments/products и GET /v1/subscriptions/plans. Витрина показывает
обычный прайс; скидочная позиция приходит только внутри самого предложения.
Подробности и требование показывать цену продления на экране: Спецпредложения.
2026-09-21 - гостевой аккаунт до регистрации¶
Пользователь генерирует, не заводя аккаунт. Ломающих изменений нет; в user появилось
одно новое поле.
POST /v1/auth/anonymous принимает обязательный device_id и необязательный
ref_code, отвечает 200 и обычным TokenResponse:
Первый вызов создаёт гостя и начисляет стартовые кредиты (сейчас 100 - бери число из
user.balance, не зашивай). Повтор с тем же device_id возвращает тот же аккаунт с тем же
балансом и новую пару токенов. Если устройство уже принадлежит полноценному аккаунту -
403 forbidden. Стартовые кредиты даются один раз на устройство за всю его жизнь:
переустановка приложения и DELETE /v1/me их не возвращают.
device_id в этом потоке - ключ от аккаунта с чужими фотографиями. Он должен быть
случайным идентификатором, который приложение сгенерировало само и держит в
Keychain/Keystore.
В user появилось поле is_anonymous (и в /v1/me, и в TokenResponse): true у
гостя, false у зарегистрированного. У гостя email и name пустые, providers - пустой
список.
Апгрейд гостя. register, google, apple, vk и yandex теперь принимают
необязательный Authorization с токеном гостя. Тогда новый аккаунт не создаётся: тот же
user.id становится полноценным, баланс, генерации и загрузки остаются, приветственные
кредиты начисляются сверх гостевых, реферальные бонусы (если гость приходил по коду)
выплачиваются в этот момент. Занятая почта по-прежнему 409 email_taken, и гость при этом
остаётся рабочим гостем. Вход в уже существующий аккаунт отдаёт токены того аккаунта, а
баланс и работы гостя туда не переносятся - предупреди пользователя до входа.
Новый код ошибки registration_required (HTTP 403) - раздел требует настоящего
аккаунта. Отвечают им платежи и подписки (кроме витрин), весь /v1/user-effects и
POST /v1/auth/change-password. Витрины GET /v1/payments/products и
GET /v1/subscriptions/plans гостю открыты: цены нужно видеть до того, как захочется
купить. Важно: не заводи этот код в обработчик 401 → refresh → повтор. Токен гостя
валиден, обновление вернёт такую же пару, и клиент зациклится.
Брошенный гость (нет входов больше 30 дней) удаляется вместе со своими фотографиями. Отсчёт от последнего входа, так что активный гость не удаляется. Подробности: Аутентификация.
2026-09-17 - вход через VK ID и Яндекс ID¶
Два новых способа входа. Одно ломающее изменение: смена пароля на аккаунте без почты (в конце записи).
POST /v1/auth/vk принимает access_token от VK ID SDK. Один роут на все три
кнопки VK ID: ВКонтакте, Одноклассники, Mail. Какой кнопкой вошёл пользователь,
передаётся в необязательном oauth_provider (vk / ok / mail, по умолчанию vk):
POST /v1/auth/yandex принимает access_token от Яндекс LoginSDK:
Оба отвечают как google и apple: 200 и TokenResponse, в том числе при создании
аккаунта. Плохой токен (включая токен Яндекса, выданный другому приложению) -
401 invalid_credentials. Провайдер не настроен на сервере или не ответил -
503 provider_unavailable. Пустой или недопустимый по формату access_token (не
печатные ASCII-символы, пробелы, длиннее 4096) или неизвестный oauth_provider - 422. Пока на
сервере не заданы id приложений VK и Яндекса, оба роута отвечают 503.
Привязка к существующему аккаунту по почте: Яндекс привязывается, как Google. VK ID пока нет: VK не сообщает, подтверждена ли почта, поэтому вход через VK создаёт отдельный аккаунт без почты. Подробности: Аутентификация.
В user появилось поле providers (и в /v1/me, и в TokenResponse): способы
входа в аккаунт. Значения email, google, apple, vk, ok, mail, yandex,
email первым, если в аккаунт можно войти по почте и паролю:
⚠️ POST /v1/auth/change-password отвечает 403 forbidden аккаунту без почты
(email: null), например созданному входом через VK ID.
Вход по паролю ищет аккаунт по почте, так что заданный без неё пароль никуда не
пускал. Отказ ничего не меняет: пароль не ставится, сессии не гасятся. Показывай
«задать пароль» только при непустом user.email.
2026-09-07 — кредит стал одним числом¶
Раньше баланс делился на три «бакета» по происхождению кредитов (paid / bonus /
earned), и одно списание могло превратиться в несколько записей журнала. Деление
обслуживало единственное правило (автору эффекта капает процент только с оплаченных
генераций), а правило теперь проверяется по истории платежей плательщика. Кошелёк
снова одно число.
⚠️ GET /v1/credits/balance больше не отдаёт paid, bonus, earned:
⚠️ GET /v1/credits/transactions: у записи больше нет поля bucket. Заодно
одна операция теперь всегда одна запись: списание под генерацию это одна строка
reserve, возврат одна строка refund с тем же reference. Прежняя рекомендация
группировать историю по reference больше не нужна, хотя ничего и не ломает.
⚠️ GET /v1/user-effects/account больше не отдаёт available (сколько
заработанного ещё лежит на балансе). Такого числа больше нет: кошелёк не помнит,
откуда пришёл каждый кредит. Текущий остаток берётся из общего
баланса, начислено и отозвано за всё время остаётся в earned и
revoked.
Значения kind в журнале не изменились. Подробности: Кредиты и
Свои эффекты.
Лимиты длины на полях запросов¶
Поля, у которых раньше не было верхней границы, теперь её имеют. Слишком длинное
значение отвечает 422, а не 500:
| Поле | Где | Лимит |
|---|---|---|
device_id |
register, login, oauth, refresh, change-password | 255 |
name |
register | 120 |
ref_code |
register, oauth | 32 |
category |
GET /v1/effects (query) |
64 |
Реальные значения (UUID устройства, имя пользователя, реферальный код из 8 символов) проходят с запасом, менять в клиенте нечего.
Заметное поведение¶
- Аватар пересжимается на сервере. Загруженный файл перекодируется в JPEG со срезанными EXIF (в том числе геометкой съёмки): по тому же ключу объекта лежит уже наш файл, а не оригинал с телефона.
- Отклонённая оплата занимает попытку. У пакетов с ограничением «один на аккаунт» неудачная попытка теперь учитывается наравне с созданной: плательщик может вернуться на страницу ПЦ и оплатить её же, поэтому вторая параллельная покупка того же пакета не разрешается.
- Подписка не залипает в
pending. Если первый платёж по подписке так и не был оплачен и его заказ уже протух, слот плана освобождается, и повторныйPOST /v1/subscriptionsбольше не отвечает409 already_subscribed. - Отказ процессинга завершает подписку честно. Документированный отказ ПЦ
(рекуррентные платежи или токены не включены, токен карты умер) обрабатывается как
неуспешное списание: работает лестница повторов, и подписка в итоге истекает, а не
остаётся
activeбез единого списания.