TL;DR. Да, App Store Connect API может обновлять поле ключевых слов, подзаголовок, описание и промотекст без ручного захода в App Store Connect. Но name и subtitle живут в ресурсе
appInfoLocalization, а keywords, description, promotionalText и What’s New — вappStoreVersionLocalization, другом ресурсе со своими правилами состояний. Попробуйте отправить PATCH на keywords для версии, которая уже опубликована или на проверке, и получите 409STATE_ERROR. API не обходит цикл релиза Apple — он просто даёт скриптовый способ запускать те же действия, которые иначе пришлось бы делать кликами.
Если вы управляете метаданными больше чем одного приложения или локализуете на шесть с лишним языков и при каждом релизе заново вбиваете одно и то же изменение поля ключевых слов в шесть вкладок локалей, App Store Connect API избавляет от кликов. От ограничений он не избавляет. Знание того, где эти ограничения реально находятся, экономит вам сессию отладки в тот день, когда PATCH-запрос упадёт по причине, не имеющей никакого отношения к вашему JSON.
Могу ли я обновить поле ключевых слов через App Store Connect API?
Да. Атрибут keywords находится в ресурсе appStoreVersionLocalization, и вы обновляете его PATCH-запросом на эндпоинт этого ресурса, привязанный к конкретной локали. Работает это точно так же, как ручное редактирование поля ключевых слов в разделе Localizable Information в App Store Connect — то же поле на 100 символов, те же правила токенизации, та же рекомендация не повторять заголовок. API ничего из этого не ослабляет — это просто другая дверь в ту же комнату.
Спотыкаются обычно не на теле запроса. Дело в том, что этот эндпоинт принимает запись только пока родительская версия App Store находится в редактируемом состоянии.
Какой ресурс отвечает за какое поле метаданных?
Это тот момент, который пропускает большинство гайдов по интеграции, — и причина, по которой рабочий PATCH-запрос для одного поля в тот же день может упасть для другого поля.
appInfoLocalization содержит поля, не привязанные к конкретной версии:
namesubtitleprivacyPolicyUrl,privacyPolicyText,privacyChoicesUrl
appStoreVersionLocalization содержит поля, привязанные к конкретной версии:
keywordsdescriptionpromotionalTextwhatsNewmarketingUrl,supportUrl
Судя по этому разделению, кажется, что name и subtitle можно публиковать независимо от подачи версии, поскольку собственная модель ресурсов Apple не вкладывает их внутрь версии. На практике это работает иначе: оба поля вступают в силу только после релиза версии — точно так же, как keywords. Если вы проверяете изменение подзаголовка и привыкли смотреть на статус версии — «Ready for Sale» — прежде чем доверять метрике, эта привычка применима и здесь: API меняет способ записи поля, а не момент, когда Apple реально его выкатывает. Это тот же разрыв между релизом и одобрением, что разобран в гайде по опциям релиза App Store Connect.
Почему мой PATCH-запрос падает с 409 STATE_ERROR?
Потому что поля appStoreVersionLocalization можно редактировать только пока родительская версия находится в редактируемом состоянии — PREPARE_FOR_SUBMISSION, DEVELOPER_REJECTED, REJECTED или METADATA_REJECTED. Как только версия переходит в WAITING_FOR_REVIEW, IN_REVIEW, PENDING_DEVELOPER_RELEASE или READY_FOR_SALE, тот же самый PATCH-запрос, который вчера работал, возвращает 409 с "code": "STATE_ERROR" и сообщением о том, что атрибут сейчас нельзя редактировать.
Это API просто применяет то же самое правило, что вы уже знаете по ручному интерфейсу: после подачи поле ключевых слов заблокировано, пока проверка не завершится или пока вы не вернёте версию через developer rejection. Решение — не другая форма запроса, а проверка статуса версии через GET перед попыткой записи, с отправкой обновления только когда статус один из редактируемых, перечисленных выше. Если вы скриптуете это как часть релизного пайплайна, эта проверка статуса должна стоять перед PATCH, а не в цикле повторных попыток после его падения.
Как на самом деле работает аутентификация?
Каждому запросу нужен JSON Web Token, подписанный ES256 и сгенерированный из приватного ключа, который вы создаёте в App Store Connect. Team Keys создаются в Users and Access → Integrations → Team Keys, и для генерации нужна роль Admin на аккаунте — требование более узкое, чем группа Account Holder/Admin/App Manager/Marketing, которая может редактировать поле ключевых слов вручную. Individual Keys, сгенерированные из вашего собственного профиля пользователя, наследуют вашу личную роль в аккаунте и назначенные вам приложения, а не доступ ко всему аккаунту.
Отсюда два практических следствия:
- Team Keys видят все приложения в аккаунте, и ограничить их одним приложением нельзя. Если вы управляете только одним приложением, Individual Key, привязанный к вашей роли App Manager или Marketing, — более узкий по охвату вариант.
- Приватный ключ скачивается ровно один раз. Apple не хранит копию для повторного скачивания. Потеряете его — придётся отозвать ключ и сгенерировать новый; ни восстановить, ни изменить уровень доступа существующего ключа задним числом нельзя.
- Apple рекомендует короткое время жизни токена для обычных запросов (App Store Connect API ограничивает обычные токены запросов примерно 20 минутами), а не максимум, допустимый для узкого набора скоупов только для чтения отчётности. Генерируйте токен на каждый запуск скрипта, а не зашивайте один токен с долгим сроком действия.
Стоит ли это вообще строить для инди-команды?
Если вы управляете одним приложением в одной-двух локалях и меняете поле ключевых слов раз в несколько недель — нет, ручной интерфейс быстрее, чем писать и поддерживать интеграцию с API. API начинает окупаться, когда вы управляете метаданными по расписанию сразу в нескольких локалях или в нескольких приложениях, где одно и то же изменение поля нужно повторить идентично много раз, а ошибку копипаста в пятой локали легко пропустить.
Реалистичный первый сценарий использования — не полная автоматизация, а скрипт, который читает текущее поле ключевых слов, описание и подзаголовок по всем локалям версии, чтобы сравнить предлагаемое изменение с тем, что реально сейчас опубликовано, прежде чем трогать что-либо вручную. У доступа на чтение нет ни одной из описанных выше проблем с блокировкой по статусу, поскольку GET-запросы работают независимо от статуса версии, — это и есть менее рискованная точка старта.
Прежде чем разрабатывать что-то под этот API
- Прежде чем писать запрос, уточните, на каком ресурсе находится нужное поле —
appInfoLocalizationдля name/subtitle,appStoreVersionLocalizationдля keywords/description/promotionalText/What’s New. - Перед каждым PATCH к
appStoreVersionLocalizationделайте GET, чтобы проверить статус версии. Пишите только когда статус —PREPARE_FOR_SUBMISSION,DEVELOPER_REJECTED,REJECTEDилиMETADATA_REJECTED. - Выбирайте между team key и individual key исходя из охвата, а не удобства — team key, который понадобился вам только для одного приложения, — это постоянный учётный ключ на весь аккаунт, который вам был не нужен.
- Помните: API меняет способ редактирования метаданных, а не момент, когда они становятся видны публично. Тайминг релиза по-прежнему подчиняется тому же правилу релиза и одобрения, что и ручное редактирование, а требования к роли по-прежнему подчиняются тем же правилам о том, кто вообще может трогать поле ключевых слов.