· Разработчикам · 6 мин чтения

Загрузка скриншотов App Store через App Store Connect API

Загрузка скриншотов App Store через App Store Connect API
TL;DR. Загрузка одного скриншота через App Store Connect API — это последовательность из четырёх шагов: создать appScreenshotSet для нужной локали и устройства, зарезервировать appScreenshot (Apple выдаёт тебе предподписанные URL для частей файла), отправить байты через PUT, а затем подтвердить резервирование PATCH-запросом с uploaded: true и MD5-хешем файла. Аутентификация — это JWT-токен на 20 минут, подписанный твоим ключом .p8. Ничего сложного, но всё это очень муторно. Вот реальная последовательность действий — и способ обойтись без неё, если строить это самому не хочется.

Документация Apple по этой теме точная, но почти нечитаемая — процесс разбросан по десятку справочных страниц без единого сквозного примера. Этот пост — тот самый разбор по шагам, с точными эндпоинтами и названиями полей. Он написан для тех, кто решает, строить ли интеграцию самостоятельно или взять готовый инструмент.

Объектная модель, в которую ты загружаешь данные

Скриншоты не привязываются к приложению напрямую. Они висят на цепочке объектов, и её нужно проходить сверху вниз:

  • appStoreVersion — конкретная версия, которую ты редактируешь (должна быть в редактируемом состоянии).
  • appStoreVersionLocalization — по одной на каждую локаль App Store (en-US, de-DE, ja и так далее).
  • appScreenshotSet — по одному на каждый тип экрана внутри локализации (6.7" iPhone, 13" iPad и т. д.).
  • appScreenshot — отдельное изображение, до 10 штук в наборе.

Так что полностью локализованный листинг на 50 языках для двух размеров iPhone и одного размера iPad — это 50 × 3 набора, в каждом из которых до 10 скриншотов. Именно из-за этого умножения люди автоматизируют процесс вместо того, чтобы кликать по веб-интерфейсу.

Шаг 0 — JWT-токен на 20 минут

Каждый запрос, кроме самой загрузки байтов, требует заголовок Authorization: Bearer с JSON Web Token, который ты подписываешь сам. Для этого нужны три вещи из App Store Connect: issuer ID, key ID и файл приватного ключа .p8, который скачивается один раз при создании ключа (повторно Apple его не покажет).

Токен подписывается алгоритмом ES256 — ECDSA на кривой P-256 с SHA-256 — с использованием этого ключа .p8. В заголовке передаётся kid; в payload — издатель как iss, аудитория в виде строки appstoreconnect-v1 и exp, отстоящий от iat не более чем на 20 минут. Apple отклоняет токены с более долгим сроком жизни, поэтому их приходится часто перевыпускать — лучше генерировать новый токен на каждую партию запросов, чем пытаться держать один открытым.

Подпиши его любой JWT-библиотекой с поддержкой ES256 (jsonwebtoken, PyJWT — что используется в твоём стеке), передай содержимое .p8 как ключ и вставляй результат в заголовок Authorization для каждого из нижеописанных запросов.

Шаг 1 — создание набора скриншотов

Набор привязан к одной локализации и одному типу экрана. Отправь POST на /v1/appScreenshotSets с полем screenshotDisplayType в attributes и связью, указывающей на нужный appStoreVersionLocalization.

Распространённые типы экранов: APP_IPHONE_67 (6.7", 1290 × 2796) — текущий размер для больших iPhone, и APP_IPAD_PRO_3GEN_129 (12.9", 2048 × 2732). Полезная деталь: теперь Apple переиспользует скриншоты для самого крупного размера iPhone в моделях меньшего класса, а самого крупного iPad — в меньших iPad, поэтому на практике часто достаточно одного набора для iPhone и одного для iPad на локаль, а не по набору на каждое физическое устройство. В ответе ты получаешь id набора, который передаёшь в следующий запрос. Если набор для этой локали/типа уже существует, используй его повторно, а не создавай дубликат.

Шаг 2 — резервирование скриншота

Файл не загружается одним запросом. Сначала ты его резервируешь: сообщаешь Apple имя файла и точный размер в байтах, а в ответ получаешь план загрузки. Отправь POST на /v1/appScreenshots с атрибутами fileName и fileSize и связью с набором из шага 1.

Самое интересное — в ответе. Внутри атрибутов нового скриншота ты получаешь uploadOperations — массив, описывающий, как именно отправлять байты. Для маленького файла это одна операция; для большого Apple разбивает его на несколько. Каждая запись содержит method (PUT), предподписанный url, байтовые offset и length для отправки, а также requestHeaders, которые нужно прикрепить.

Шаг 3 — отправка байтов через PUT

Для каждой операции считай участок файла начиная с offset длиной length байт и отправь его PUT-запросом по указанному url строго с теми requestHeaders, которые предоставила Apple. Эти URL предподписаны, поэтому JWT здесь передавать не нужно — добавление заголовка Authorization может даже сломать подписанный запрос. Несколько операций можно выполнять параллельно, но Apple ограничивает скорость при агрессивной загрузке, так что лучше оборачивать их в повтор с задержкой, а не отправлять всё разом.

Шаг 4 — коммит с uploaded: true и MD5

Загрузка байтов сама по себе ничего не даёт, пока ты не сообщишь Apple, что резервирование завершено. Отправь PATCH на скриншот с uploaded: true и sourceFileChecksum — MD5-хешем всего файла в виде строки из строчных шестнадцатеричных символов. Это проверка целостности от Apple: если контрольная сумма не совпадает с тем, что получилось на их стороне, обработка завершится ошибкой.

После PATCH-запроса файл уходит в обработку на стороне Apple. Опрашивай поле assetDeliveryState скриншота, пока оно не перейдёт в завершённое состояние — и читай errors, если обработка не удалась: именно там всплывают проблемы с неверными размерами и альфа-каналом. PUT и PATCH могут пройти успешно, а скриншот всё равно будет отклонён через несколько минут в процессе обработки. Не считай, что код 2xx на коммите означает завершение — следи за статусом доставки.

Шаг 5 — порядок отображения

Скриншоты возвращаются в том порядке, в котором были созданы, а это редко совпадает с нужным порядком показа. Порядок набора — отдельная связь. Отправь PATCH на /v1/appScreenshotSets/{id}/relationships/appScreenshots с массивом ID скриншотов — порядок элементов массива и есть порядок отображения в магазине.

Ограничения, которые Apple действительно проверяет

  • Только PNG или JPEG. Никаких HEIC и WebP. Если твой рендерер выдаёт что-то другое, сначала конвертируй.
  • Без альфа-канала. Убери прозрачность на сплошной фон и экспортируй в RGB. Случайный альфа-канал — одна из самых частых незаметных причин отказа при обработке.
  • Точные размеры для каждого типа экрана. Изображение должно точно соответствовать пиксельному размеру типа экрана — без допусков и без масштабирования вверх. Файл 1290 × 2796 подходит только в набор APP_IPHONE_67 и никуда больше.
  • До 10 штук в наборе. Максимум десять скриншотов на локализацию на тип экрана.
  • Только редактируемая версия. Изменять наборы можно только в версии, которая находится в редактируемом состоянии; версия, уже отправленная на проверку, заблокирована.

Почему это сложнее, чем кажется

Каждый отдельный запрос прост. Сложность — во всём, что вокруг: подпись и обновление токена каждые 20 минут, вычисление MD5, разбиение файлов в соответствии с uploadOperations, обработка повторов при частичной загрузке, опрос статуса доставки, сопоставление твоих локалей с кодами локалей Apple — и всё это по 150+ раз для полностью локализованного листинга. Чтобы довести это до надёжной работы, уйдут целые выходные — на аутентификацию, загрузку по частям и обработку ошибок, а дальше это придётся поддерживать при каждом новом типе экрана от Apple.

Если нужна эта последовательность без написания кода, команда deliver из Fastlane оборачивает тот же самый API и является стандартным путём с открытым исходным кодом. Она решает задачу загрузки, но не рисует скриншоты и не пишет тексты описаний.

…или обойтись без всего этого

API умеет только переносить готовые изображения. Кто-то всё равно должен спроектировать карусель, написать тексты и перевести их на каждую локаль — а именно это и отнимает больше всего времени. Mokbi делает это уже сегодня: проектирует скриншоты, пишет тексты для листинга и переводит всё это на 50 языков, а затем экспортирует каждое изображение в точных размерах, которые требует этот API для каждого типа экрана — PNG, RGB, без альфа-канала, нужный размер — так что загрузка не отклоняется на этапе проверки статуса доставки.

А затем публикует. Для App Store Mokbi выполняет под капотом именно эту последовательность reserve-and-commit — загружает твои скриншоты и метаданные и готовит версию к отправке, так что тебе никогда не приходится трогать ни JWT-токен, ни загрузку по частям. Финальный Submit и саму проверку Apple по-прежнему должен инициировать ты сам — всё, что до этого, уже сделано за тебя. Для Google Play публикация идёт напрямую в магазин. В любом случае ты получаешь результат работы API без написания кода — с уже готовыми дизайном, текстами и переводом на 50 языков.

Что почитать дальше

Открыть редактор →