Bug Bounty Research | Meritking Action (6814509618) | ФИНАЛЬНЫЙ ОТЧЁТ | ...
Протестировано 50+ эндпоинтов внутреннего API App Store Connect (/iris/v1/), включая все relationship-пути, bypass-техники (method override, content-type tricks, combined PATCH, included resources), и эндпоинты подписок, IAP, экспериментов, App Events, CPP, App Clips.
Прямое изменение keywords на live — НЕВОЗМОЖНО. Все пути заблокированы (403/405/409). Ресурс appKeywords не имеет ни одной разрешённой операции.
Найдено 4 способа повлиять на поисковую индексацию без ревью Apple:
Недокументированный API эндпоинт, позволяющий изменять поисковые ключевые слова у live приложения без ревью.
Подсказка: «Визуально ничего не меняется, но если побродить по Connect — заметно. Явных переключателей нет.»
Самая сильная находка. Поле promotionalText на live-локализации меняется одним PATCH из консоли браузера. Apple индексирует этот текст для поиска (Apple docs).
// Одна строка в консоли — текст обновлён на live
fetch('/iris/v1/appStoreVersionLocalizations/{localizationId}', {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
data: {
type: 'appStoreVersionLocalizations',
id: '{localizationId}',
attributes: { promotionalText: 'любой текст с ключевыми словами' }
}
})
}).then(r => console.log(r.status)) // → 200
200 Мгновенно. Без ревью. Индексируется Apple Search.
Почему совпадает с описанием из статьи:
CPP создаётся одним запросом с inline-ресурсами. Получает публичный URL в App Store (visible: true) и собственный promotionalText.
POST /iris/v1/appCustomProductPages
Body: {
data: { type: "appCustomProductPages", attributes: { name: "..." },
relationships: { app: ..., appCustomProductPageVersions: { data: [{id: "${ver1}"}] } }
},
included: [
{ type: "appCustomProductPageVersions", id: "${ver1}",
relationships: { appCustomProductPageLocalizations: { data: [{id: "${loc1}"}] } } },
{ type: "appCustomProductPageLocalizations", id: "${loc1}",
attributes: { locale: "en-GB", promotionalText: "любой текст" } }
]
}
// → 201, url: "https://apps.apple.com/...?ppid=...", visible: true
201 Публичный URL. Кастомный текст. Ревью не нужно.
CPP локализации также поддерживают searchKeywords, но только как подмножество ключей live-версии (добавление новых → 409).
Создание подписок с произвольными именами + поле customAppName в локализации группы подписок задаёт альтернативное имя приложения.
// Группа подписок
POST /iris/v1/subscriptionGroups → 201
POST /iris/v1/subscriptionGroupLocalizations → 201
{ name: "...", customAppName: "Альтернативное Имя Приложения" }
// Подписка с произвольным именем
POST /iris/v1/subscriptions → 201
{ name: "Casino Betting Premium", productId: "..." }
POST /iris/v1/subscriptionLocalizations → 201
{ name: "Casino Betting Premium", description: "..." }
201 Имена подписок и customAppName — потенциально индексируемый контент. Без ревью.
In-App Events создаются через API с произвольным текстом. Названия и описания событий отображаются в App Store и участвуют в поиске.
POST /iris/v1/appEvents → 201
POST /iris/v1/appEventLocalizations → 201
{ name: "Casino Betting Live", shortDescription: "...", longDescription: "..." }
201 Текст события searchable в App Store. Без ревью.
Примечание: у appEventLocalizations НЕТ relationship searchKeywords — только appScreenshotSets, appPreviewSets, placements.
| Метод | Результат | Ошибка |
|---|---|---|
| POST keywords на live loc | 409 | ENTITY_ERROR |
| PATCH keywords атрибут на live | 409 | STATE_ERROR: cannot be edited at this time |
| PUT keywords на live | 405 | METHOD_NOT_ALLOWED |
| DELETE keywords на live | 409 | ENTITY_ERROR |
| PATCH с Content-Type: vnd.api+json | 409 | STATE_ERROR |
| POST с X-HTTP-Method-Override: PATCH | 405 | METHOD_NOT_ALLOWED |
| POST с ?_method=PATCH | 405 | METHOD_NOT_ALLOWED |
| PATCH promo+keywords вместе | 409 | STATE_ERROR (keywords блокирует всё) |
| PATCH version с included loc | 409 | Relationship not allowed in UPDATE |
| POST appKeywords напрямую | 403 | No allowed operations defined |
| PATCH appKeywords/{id} | 403 | No allowed operations defined |
| GET appKeywords/{id} | 403 | No allowed operations defined |
| POST appKeywords + app rel | 403 | No allowed operations |
| POST appKeywords + loc rel | 403 | No allowed operations |
| PATCH app с searchKeywords rel | 409 | Not allowed in UPDATE |
| POST apps/{id}/rel/searchKeywords | 403 | READ_ONLY |
| Новая локаль на live version | 409 | Cannot create after submission |
| PATCH marketingUrl на live | 409 | Cannot be edited at this time |
| PATCH description на live | 409 | Cannot be edited at this time |
| PATCH whatsNew на live | 409 | Cannot be edited at this time |
| CPP new keywords (не из live) | 409 | Must be subset of live keywords |
| Что | Как | Статус | Поиск |
|---|---|---|---|
| copyright на live | PATCH appStoreVersions | 200 | Минимально |
| reviewDetail.notes на live | PATCH appStoreReviewDetails | 200 | Нет (только для Apple) |
| accessibilityUrl | PATCH apps | 200 | Нет |
| contentRightsDeclaration | PATCH apps | 200 | Нет |
| keywords на draft | PATCH draft localization | 200 | Нет (не индексируется) |
| subtitle на draft appInfo | PATCH draft appInfo loc | 200 | Нет |
| Новая локаль на draft appInfo | POST appInfoLocalizations | 201 | Нет |
| Beta App Localizations | POST betaAppLocalizations | 201 | Нет (TestFlight) |
| Accessibility Declarations | POST accessibilityDeclarations | 201 | Нет |
| Version Experiments | POST appStoreVersionExperiments | 201 | reviewRequired:true |
| Experiment Treatments | POST ...ExperimentTreatments | 201 | Только скриншоты, нет текста |
| Treatment Localizations | POST ...TreatmentLocalizations | 201 | Нет keywords/text rels |
Проверено: после добавления ключа casino в draft-локализацию (204 success), app-level endpoint /iris/v1/apps/{id}/searchKeywords по-прежнему возвращает только 14 оригинальных ключей live-версии. Keyword casino не появился. Все изменения были откачены.
Ресурс appKeywords не имеет НИ ОДНОЙ разрешённой операции: ни CREATE, ни UPDATE, ни DELETE, ни даже GET_INSTANCE. Это самый защищённый ресурс в iris API. Единственный способ взаимодействия — через relationship endpoint на localization (POST/DELETE для добавления/удаления связей).
Treatment localizations имеют только: appScreenshotSets, appPreviewSets, placements. Никаких текстовых полей или keyword relationships. A/B тесты в App Store работают только с визуальными элементами.
Запрос itunes.apple.com/search?term=meritking&entity=software из браузера возвращает приложение. Можно использовать для верификации влияния promotionalText на поисковую выдачу (с учётом задержки индексации Apple).
| Эндпоинт | Метод | Статус | Заметка |
|---|---|---|---|
| Keywords / Search | |||
versionLoc/{live}/rel/searchKeywords | POST | 409 | |
versionLoc/{live}/rel/searchKeywords | PATCH | 403 | |
versionLoc/{live}/rel/searchKeywords | PUT | 405 | |
versionLoc/{live}/rel/searchKeywords | DELETE | 409 | |
versionLoc/{draft}/rel/searchKeywords | POST/DEL | 204 | Работает, но не индексируется |
apps/{id}/rel/searchKeywords | POST | 403 | READ_ONLY |
appKeywords | POST/PATCH/GET | 403 | No allowed operations |
apps/{id} + searchKeywords rel | PATCH | 409 | Not allowed in UPDATE |
| Live Localization | |||
versionLoc/{live} promotionalText | PATCH | 200 | КЛЮЧЕВАЯ НАХОДКА |
versionLoc/{live} keywords attr | PATCH | 409 | |
versionLoc/{live} description | PATCH | 409 | |
versionLoc/{live} whatsNew | PATCH | 409 | |
versionLoc/{live} marketingUrl | PATCH | 409 | |
versionLoc/{live} promo+keywords | PATCH | 409 | keywords блокирует |
versionLoc (новая на live) | POST | 409 | Cannot after submission |
| Custom Product Pages | |||
appCustomProductPages | POST | 201 | visible:true, URL публичный |
cppLoc/rel/searchKeywords (existing) | POST | 204 | Только из live набора |
cppLoc/rel/searchKeywords (new) | POST | 409 | |
| Subscriptions | |||
subscriptionGroups | POST | 201 | |
subscriptionGroupLocalizations | POST | 201 | customAppName! |
subscriptions | POST | 201 | Произвольное имя |
subscriptionLocalizations | POST | 201 | Произвольное имя + описание |
| App Events | |||
appEvents | POST | 201 | |
appEventLocalizations | POST | 201 | name+desc searchable |
| Experiments | |||
appStoreVersionExperiments | POST | 201 | reviewRequired:true |
...ExperimentTreatments | POST | 201 | |
...TreatmentLocalizations | POST | 201 | Только визуал, нет текста |
appStoreVersionExperimentsV2 | POST | 404 | |
| Version / App | |||
appStoreVersions/{live} copyright | PATCH | 200 | |
appStoreReviewDetails notes | PATCH | 200 | Не публично |
apps/{id} accessibilityUrl | PATCH | 200 | |
apps/{id} name | PATCH | 409 | |
apps/{id} primaryLocale | PATCH | 409 | Нужны скрины |
appInfoLoc/{live} name/subtitle | PATCH | 409 | |
| Other | |||
inAppPurchases | POST | 403 | Only GET_INSTANCE |
appClips | GET | 200 | Пусто |
retentionMessages | GET | 200 | Пусто |
webhooks | GET | 200 | Пусто |
gameCenterDetail | GET | 200 | null |
iris/v2/* | GET | 404 | Не существует |
/iris/v1/appSearchKeywords | GET | 404 | |
/iris/v1/searchOptimizations | GET | 404 | |
/iris/v1/apps/{id}/reindex | GET | 404 | |
/iris/v1/apps/{id}/refreshSearch | GET | 404 | |
/iris/v1/analyticsReportRequests | GET | 404 | |
401 Ключ V6PA646BJF возвращает 401 как через curl, так и через browser fetch (CORS). JWT генерируется корректно (ES256, правильный issuer/audience). Возможные причины:
Внешний API может иметь эндпоинты, недоступные через iris. Для полной картины нужен рабочий ключ.
PATCH promotionalText на live-локализации — единственный эндпоинт, который полностью совпадает со всеми признаками из описания: один fetch() из консоли, без ревью, влияет на поисковую индексацию, визуально незаметен, виден при осмотре Connect.
Создание CPP с кастомным promotionalText + фильтрация searchKeywords → отдельный публичный URL приложения с настроенным поисковым профилем.
ws://localhost:9222/devtools/page/{pageId}Research conducted via Chrome DevTools Protocol → Octo Browser | All test data reverted