Обновление интеграции · 21 октября 2026
Контракт 1.11.0. Ключи, адреса API и существующие запросы прежние. Справка, ZIP и AI_INTEGRATION_TASK.md синхронизированы: отзыв доступа, бесплатная автозамена и получение актуального пароля.
Уже подключены? Обновляйте прежнюю заявку и её группы, не создавайте новую покупку автоматически. В архиве начните с ACCOUNT_ACCESS_AND_REPLACEMENT.md; для ИИ передайте обновлённое задание. Снимки openapi.json и API_DOCUMENTATION.html действуют на дату сборки, актуальные данные проверяйте по API.
Поля блокировки и ошибки · Как обновлять ссылки без залипания блокировки
С чего начать
Для подключения достаточно основного API-ключа, адреса API и готового комплекта выше. Резервный ключ хранится отдельно и используется только если основной потребуется заменить.
Основной адрес
https://api.installios.ru/v1
Используйте его во всех новых интеграциях.
Резервный адрес API
https://installios.ru/api/partner/v1
Используйте его, если основной адрес временно недоступен.
Заголовки каждого запроса
Authorization: Bearer iosp_ВАШ_ОСНОВНОЙ_КЛЮЧ Accept: application/json Content-Type: application/json X-Request-ID: ваш-идентификатор-запроса
Не размещайте ключ в браузере или мобильном приложении. Все запросы выполняются только с вашего сервера. API-ключ, webhook-секрет, логины, пароли и коды нельзя писать в журналы.
Навигация
Как проходит одна установка
fallback_url — готовая нейтральная инструкция с QR, реквизитами и кнопками установки.code.ready.Первый тест за 15 минут
Тестовый партнёр работает в sandbox: сумма рассчитывается, но реальный долг не создаётся.
- Скачайте комплект и укажите ключ в
.envили в окружении Postman. - Запустите
START_TEST_WINDOWS.cmdлибо коллекцию Postman «01 — безопасная проверка». - Убедитесь, что API отвечает, каталоги получены, заявка создана, повтор не создал дубль, страница и QR открываются.
- Для реального теста кода сначала вызовите
POST /orders/{id}/code-waits, затем запросите код на iPhone и проверяйтеGET /code-waits/{wait_id}.
Успешный результат: ответы 2xx, повторный заказ вернул тот же id, код не старше 60 секунд, а balance.debt_rubles в sandbox остался равен нулю.
1. Каталоги и приложения
GET/catalogs
curl -sS "$API_BASE/catalogs" \ -H "Authorization: Bearer $API_KEY"
{
"ok": true,
"data": {
"catalogs": [
{"id": "cat_...", "name": "maxim", "applications_count": 18}
]
}
}
GET/catalogs/{catalog_id}/applications
{
"ok": true,
"data": {
"catalog": {"id": "cat_...", "name": "maxim"},
"applications": [{"id": "app_...", "name": "ВКонтакте"}]
}
}
Иконки приложений — необязательно
Чтобы получить готовые картинки, добавьте ?include_icons=1:
GET /catalogs/{catalog_id}/applications?include_icons=1
{"id": "app_...", "name": "ВКонтакте", "icon_url": "https://installios.ru/partner-assets/icons/app_....png"}
Формат ответа: без include_icons=1 каждый элемент содержит id и name. С параметром include_icons=1 дополнительно приходит icon_url.
Единый каталог в вашей системе — обязательно
Один продукт — один application.id. Одинаковое клиентское приложение может физически находиться в нескольких каталогах и иметь разные сборки, но API возвращает для него один логический app_... ID. В вашей витрине должна быть одна карточка такого приложения.
- Получите все элементы из
data.catalogs. - Для каждого каталога загрузите
data.applications. Нельзя читать только первый каталог. - Сложите приложения в словарь, где ключ —
application.id. Повторный ID добавляет источник к уже существующему товару, а не новую карточку. - В режиме
source_mode: autoотправляйте только ID приложения безcatalog_id. Источник и Apple-учётку выберет Install-IOS.
catalogs = GET /catalogs -> data.catalogs
next_by_id = {}
for catalog in catalogs:
applications = GET /catalogs/{catalog.id}/applications -> data.applications
for application in applications:
product = next_by_id.get_or_create(application.id, application)
product.catalog_ids.add(catalog.id) # только для вашей диагностики
visible_catalog = values(next_by_id) # заменить одним действием
Обновление: выполняйте полную синхронизацию при запуске, по ручной кнопке и каждые 5–10 минут. Новый снимок сначала собирайте отдельно. Если хотя бы один запрос не удался, оставьте последний полностью исправный кэш и повторите позже — частичный список публиковать нельзя.
Правило объединения: используйте только application.id. Не объединяйте и не разделяйте приложения по названию, версии, внутреннему UID или catalog_id. applications_count показывает число приложений отдельного источника, а размер общей витрины равен числу уникальных application.id.
Если один каталог отключён, приложение остаётся доступным, пока тот же ID присутствует в другом разрешённом каталоге. При объединении одинаковых ID сохраняйте первый непустой icon_url. Если поле равно null или картинка временно недоступна, показывайте аккуратный fallback. Для строгой CSP разрешите изображения с https://installios.ru.
API намеренно не передаёт внутренние UID, IPA-адреса, версии и технические данные учёток.
2. Создание и просмотр заявки
POST/orders
Автоматический выбор учёток — рекомендуемый режим
{
"external_order_id": "SC-10482",
"source_mode": "auto",
"applications": [
{"id": "app_..."},
{"id": "app_..."}
]
}
Равномерная ротация: каждая новая общая заявка получает следующий доступный каталог по кругу. Все приложения одной заявки сначала направляются в этот каталог; если конкретного приложения там нет, API берёт следующий подходящий каталог. Отключённые и технически неготовые каталоги пропускаются.
Несколько частей одной покупки: если приложения одной клиентской заявки отправляются отдельными запросами, используйте один стабильный UUID и номера частей: <service>-<uuid>-1, <service>-<uuid>-2, <service>-<uuid>-3. <service> замените на короткий префикс своей системы, а <uuid> — на один и тот же UUID клиентской покупки. Это шаблон, его нельзя отправлять буквально. Тогда все части сохранят один основной каталог, а очередь передвинет только следующая новая покупка.
Ручной выбор каталога
{
"external_order_id": "SC-10483",
"source_mode": "manual",
"applications": [
{"id": "app_...", "catalog_id": "cat_..."}
]
}
В одной заявке можно передать до 50 приложений. API сам объединит их в группы по Apple-учёткам.
Защита от дублей: при сетевом повторе отправляйте тот же external_order_id. Первый ответ будет HTTP 201, повторный — HTTP 200 с тем же id. Новый внешний номер при повторе создавать нельзя.
Что приходит в ответе
idзаявки и актуальный срокexpires_at;initial_ttl_seconds— срок до первого ожидания кода,code_window_ttl_seconds— срок после него;- группы по учёткам и приложения внутри них;
- основная и резервная короткие ссылки каждого приложения;
fallback_url— готовая полная инструкция для клиента;qr_urlдля открытия этой страницы на другом устройстве;- расчётная и фактически начисленная сумма в копейках.
Просмотр состояния
GET/orders/{id}
Возвращает актуальный статус, срок, группы, приложения, ссылки и состояние начислений одной заявки.
GET/orders?limit=100
Возвращает последние заявки партнёра. По умолчанию API выдаёт 100 записей.
Логины, пароли и коды в эти ответы не входят. Временные реквизиты доступны только через отдельный защищённый endpoint ниже.
Таймер задаётся сервером: используйте expires_at, initial_ttl_seconds, code_window_ttl_seconds, order_expires_at и индивидуальный groups[].expires_at, когда он есть. Не переносите розничное правило «6 часов после оплаты» на Partner API. Повторные запросы не продлевают срок.
3. Реквизиты, клиентская страница и повторная выдача
GET/orders/{id}/credentials
Возвращает логин и пароль отдельно для каждой группы. Чтение доступно только пока заявка активна и всегда фиксируется в аудите. После сохранения владельцем нового пароля этой же Apple-учётки действующая выдача возвращает актуальный пароль. Запрашивайте реквизиты заново перед показом клиенту; не используйте сохранённую копию. Установочная страница получает обновление при перезагрузке. Замена учётки каталога не подменяет учётку старой заявки; закрытые и истёкшие ссылки не восстанавливаются.
POST/orders/{id}/set_extend
Разрешает следующую повторную выдачу на 48 часов от этого момента. Использовать разрешение можно сразу. Текущие ссылки, реквизиты и таймер заявки не меняются.
POST/orders/{id}/extend
Когда клиент готов устанавливать, расходует текущее разрешение и повторно выдаёт всю заявку на 12 часов. API создаёт новые прямые ссылки и новый набор временных реквизитов.
Перевыдача одного приложения
Добавьте {"application_id":"app_..."} в тело обоих запросов set_extend и extend. ID берётся из groups[].applications[].id заказа.
Две перевыдачи на каждое приложение; разрешение действует 48 часов, новые ссылки — 12 часов с фактической выдачи. Ссылки, сроки и попытки остальных приложений сохраняются. Ранее использованные попытки учитываются.
Для кнопки используйте groups[].applications[].reissue, а после выбранной операции — application_reissue. Обновите ссылки, group_id и таймер по groups[].expires_at. Повтор завершённого extend возвращает прежний результат с issued_now:0.
Без application_id работает прежний режим всей покупки. Незавершённый общий частичный цикл сначала завершите либо дождитесь истечения его разрешения. При неизвестном результате сначала проверьте GET, не создавайте новое разрешение автоматически. Подробности и новые примеры клиентов — APPLICATION_REISSUE.md в архиве интеграции.
Правильный порядок: когда оператор одобрил повторную выдачу, вызовите POST /orders/{id}/set_extend. В течение следующих 48 часов клиент может выбрать удобный момент, в том числе сразу после разрешения. Только когда он действительно готов устанавливать, вызовите POST /orders/{id}/extend — с этого момента начнутся ровно 12 часов.
set_extend безопасно повторять: пока разрешение активно, API вернёт prepare_applied=false и не передвинет его срок. Если 48 часов закончились, а лимит перевыдач не исчерпан, вызовите set_extend снова. Вызов extend без активного разрешения вернёт HTTP 409, а с истёкшим разрешением — HTTP 410.
На одну покупку доступны две фактические перевыдачи сверх первоначальной выдачи. Перед каждой нужно отдельное разрешение set_extend; успешный extend расходует это разрешение. Если партнёр передавал одну покупку частями с общим UUID и суффиксами -1, -2 и далее, достаточно подготовить и перевыдать любую часть — будут обработаны все части покупки. После каждой перевыдачи снова доступны до трёх успешных получений кода. Запуск ожидания кода не сокращает и не продлевает 12-часовой срок.
Сначала API подбирает другой каталог с другой Apple-учёткой. Если для полного набора приложений альтернативы нет, используется исходный каталог, но пакет создаётся заново с актуальными на этот момент логином и паролем. Поэтому после успешного ответа обязательно замените сохранённые прямые ссылки данными из ответа и повторно запросите GET /orders/{id}/credentials.
Идентификаторы заявки и групп, клиентский fallback_url и QR остаются прежними. Старые ссылки отключаются только после успешного создания и сохранения всех новых. Если перевыдача не удалась, исходная заявка не меняется. Повтор extend после первой выдачи без нового разрешения вернёт HTTP 409 extension_not_prepared. После двух выдач set_extend и extend вернут HTTP 409 already_extended. Уже учтённые установки повторно не начисляются.
В ответе заявки repeat_count показывает использованные перевыдачи всей покупки, repeat_limit равен 2, repeat_remaining — остаток. Части одной покупки используют общий лимит. extended_at — время последней успешной перевыдачи.
Если ответ потерялся из-за сети, сначала получите GET /orders/{id} и проверьте счётчик и состояние заявки. Не запускайте автоматически новую пару set_extend и extend: она может потратить следующую перевыдачу без нового решения оператора.
Политика хранения: реквизиты и коды используйте только в оперативной памяти. После окончания заявки их необходимо удалить из вашей системы, логов, аналитики и резервных копий.
Если не хотите собирать собственный интерфейс установки, просто откройте клиенту fallback_url. На странице уже есть QR, инструкция, логин, пароль, получение кода и обе ссылки каждого приложения.
Частичная перевыдача — дополнительный режим
Старый режим выше сохраняется. Для частичной выдачи передайте в POST /orders/{id}/extend JSON {"allow_partial":true}. Отсутствующий параметр или false означают прежнюю выдачу всего комплекта.
Читайте data.repeat_result: status равен complete, partial или unavailable. Даже HTTP 200 / ok:true может означать, что ни одно приложение пока недоступно. Массив applications содержит все части покупки: order_id, external_order_id, application_id, название, свежий group_id, статус, ссылки и индивидуальный expires_at.
Недоступная позиция имеет status:temporarily_unavailable, reason:no_available_catalog, пометку «Временно недоступно» и links:null. Оставьте её в покупке и предложите проверить снова. В новом режиме группы могут разделяться: старые group_id не кешируйте.
Повторяйте extend с тем же флагом, пока repeat_result.retry_allowed=true. Новый set_extend для недостающих позиций не нужен. Уже выданные ссылки и коды остаются прежними. Первый успешно выданный элемент расходует одну из двух перевыдач; остальные завершают эту же попытку бесплатно по лимиту, даже при repeat_remaining=0. Если недоступно всё, лимит не расходуется.
Дедлайн eligible_until остаётся исходным: 48 часов от разрешения. Каждая ссылка действует 12 часов от своей выдачи. issued_now показывает новые позиции этого запроса; issued — весь результат попытки. Статус issued не отменяет проверку срока ссылки.
GET заявки также возвращает repeat_result. Повтор завершённой попытки ничего не выдаёт заново. Новый set_extend после завершения или истечения окна начинает следующий полный цикл, если остался лимит. Пока есть недостающие позиции и окно активно, set_extend сохраняет текущую попытку.
Webhook order.extended приходит для частей, получивших новые ссылки; дополнительные поля: allow_partial, repeat_attempt_id, repeat_status, issued_now, issued, pending. После него получите GET заявки. Ошибки хранилища и записи возвращают ошибку без фиксации частичного результата этого запроса. Возвраты не выполняются автоматически.
Полный пример и правила повторов — файл PARTIAL_REISSUE.md в наборе интеграции. Параметр в клиентах набора: Python extend(id, allow_partial=True), Node extend(id, true), PHP extend($id, true).
4. Получение кода
Правильный порядок: сначала API включает ожидание, затем клиент запрашивает код на iPhone. Код, появившийся раньше ожидания, не выдаётся.
POST/orders/{id}/code-waits
Idempotency-Key: code-SC-10482-group-1
{"group_id": "grp_..."}
В ответе приходит order_expires_at. У исходной заявки первый успешный запуск задаёт новый сокращённый срок всей заявки. После фактической повторной выдачи срок уже равен 12 часам и запуск ожидания кода его не сокращает. Всегда обновляйте таймер значением из ответа API.
Сохраните полученный wait_id и проверяйте его раз в секунду:
GET/code-waits/{wait_id}
| Статус | Что делать |
|---|---|
active | Продолжать polling раз в секунду. Клиент уже может запросить код. |
succeeded | Сразу показать поле code клиенту. Учитывайте valid_for_seconds. |
code_expired | Код старше минуты. Создать новое ожидание и попросить клиента запросить новый код. |
timed_out | За время ожидания код не появился. Повторить процесс по действию клиента. |
cancelled | Ожидание отменено. Не продолжать polling; при необходимости создать новую попытку. |
Активное ожидание работает до 5 минут. На одной учётке одновременно может ждать любое количество клиентов; один свежий код получат все ожидания, включённые до его появления. Для одной группы разрешено не более трёх успешных получений в текущем окне. После каждой из двух повторных выдач лимит текущего окна начинается заново, а поле successful_codes_total сохраняет историю.
5. Webhook — необязательное ускорение
Polling полностью поддерживается. Webhook позволяет получать события быстрее и использовать polling только как страховку.
События: order.created, order.extended, order.expired, code.ready, billing.charged, billing.reversed, payment.recorded, order.completed.
Автозамена при блокировке учётки
Сервис сам заменяет только ещё действующие боевые API-выдачи со статусом active. Меняются только приложения заблокированной учётки; остальные части заявки, история и начисления сохраняются. Набор может быть распределён по разным разрешённым партнёру рабочим каталогам. Две обычные перевыдачи не расходуются. Срок доступа не продлевается: остаток округляется вниз до минуты; выдачи с остатком меньше 65 секунд не заменяются.
Новая заявка не создаётся. Читайте прежний GET /orders/{order_id}: в groups[].applications[].links будут новые ссылки, ID групп могут поменяться. Поле automatic_replacement содержит status (pending, partial, replaced), счётчики pending/replaced, limit_consumed: false и applications[] с application_id, status, expires_at. Если замены нет, очередь ждёт её до прежнего срока; старые ссылки/ожидания остаются закрытыми. Возвраты pending/approved/settled, completed, истёкшие и тестовые заявки не заменяются.
На каждую успешно заменённую позицию отправляется обычный order.extended с automatic: true, reason: "account_blocked", limit_consumed: false, automatic_replacement_id, application_id, group_id и expires_at. После него перечитайте всю заявку и замените сохранённые группы, ссылки и реквизиты. Без настроенного webhook периодически опрашивайте GET до замены или истечения срока. Повторная доставка webhook не означает новую перевыдачу. Старые закрытые ссылки не восстанавливаются, даже после разблокировки учётки.
| Заголовок | Назначение |
|---|---|
X-InstallIOS-Event | Уникальный ID события. Используйте для защиты от повторной обработки. |
X-InstallIOS-Timestamp | Unix-время создания подписи. |
X-InstallIOS-Signature | sha256=HMAC-SHA256(secret, timestamp + "." + raw_body) |
Проверяйте подпись по точному сырому телу до JSON-разбора, отклоняйте слишком старый timestamp и отвечайте HTTP 2xx только после безопасного сохранения события. Повторы одного X-InstallIOS-Event не должны менять результат.
Возвраты — отдельный учёт
Кнопка «Запросить возврат» на стороне партнёра вызывает POST /orders/{order_id}/refund с обычным API-ключом и JSON {"reason":"Причина возврата"}. API-ключ храните только на своём сервере, не в браузере покупателя.
Сумма определяется по фактическим неотменённым начислениям всей логической заявки на момент запроса. Части одной UUID-покупки объединяются. Сумму передавать не нужно. Ответ 201 означает новый запрос, 200 — уже существующий: повтор не создаёт дубль. При потерянном ответе сначала проверьте GET /orders/{order_id}/refund. Нулевые, тестовые и уже отменённые начисления не возвращаются.
Статусы: pending — на рассмотрении; approved — владелец подтвердил; rejected — отклонён; settled — владелец отметил полное погашение. Отклонённый запрос сохраняется; повтор по той же покупке вернёт его, а не новый запрос.
GET /refunds?limit=50&offset=0 возвращает свою историю, has_more и totals: pending_kopecks, approved_kopecks, settled_kopecks, remaining_kopecks. Все эти суммы в копейках. Подтверждение, отклонение и погашение доступны только владельцу в админке.
Запрос и одобрение сами по себе не меняют основной баланс. После одобрения владелец может отдельно зачислить непогашенный остаток на баланс: долг уменьшится, остаток станет авансом. В /ledger появится отрицательная запись kind: "refund_credit" с ID возврата в reference; актуальная сумма доступна в /balance. Возврат перейдёт в settled. Повторное зачисление исключено. Это внутренний зачёт, не денежный перевод покупателю.
Владелец также может отметить расчёт вне системы — такая ручная отметка меняет только реестр возвратов, без записи в основной баланс. Поэтому settled означает полное погашение любым из этих способов; способ проверяйте по журналу. Чтение GET /refunds не меняет баланс (main_balance_changed: false описывает сам GET). Уведомления webhook о возвратах не отправляются: проверяйте статус и баланс запросами GET. Полная схема — в OpenAPI 1.10.0.
6. Финансы
GET/balance · GET/ledger · GET/ledger.csv
Тариф фиксируется для каждого приложения при создании заявки. В рабочем режиме каждое приложение начисляется отдельно при первом переходе по его основной или резервной кнопке «Установить». Повторные нажатия этого же приложения не создают новый долг. Успешный код остаётся независимой контрольной метрикой.
Событие billing.charged может прийти несколько раз для одной заявки — максимум один раз на каждое приложение. В событии передаются приложение и сумма; защищайтесь от повторной обработки по X-InstallIOS-Event.
GET /balance возвращает текущее финансовое состояние партнёра в рублях:
| Поле | Значение |
|---|---|
net_rubles | Итоговый баланс: положительное значение — задолженность, отрицательное — аванс. |
debt_rubles | Текущая задолженность. |
advance_rubles | Текущий аванс. |
currency | Валюта расчёта — RUB. |
GET /ledger возвращает журнал начислений, отмен и оплат в JSON. GET /ledger.csv выгружает тот же финансовый журнал в CSV для сверки.
В журнале, webhook-событиях и данных заявки денежные поля с суффиксом _kopecks передаются целым числом копеек: 10000 соответствует 100 ₽.
{
"ok": true,
"data": {
"net_rubles": -128200.00,
"debt_rubles": 0.00,
"advance_rubles": 128200.00,
"currency": "RUB"
}
}
Аккаунт заблокирован
Владелец отдельно разрешает эту функцию доверенным боевым партнёрам. В sandbox она отключена. В ответе заявки у каждой группы есть can_report_blocked и account_report_reference.
- Загрузите
GET /orders/{order_id}. Еслиcan_report_blocked=true, покажите оператору возле этой учётки кнопку «Аккаунт заблокирован». - Попросите подтвердить: «Apple сообщает о блокировке аккаунта? Новые выдачи этой учётки будут остановлены во всех каналах».
- С вашего сервера отправьте запрос с тем же ключом партнёра и значением из выбранной группы:
POST/orders/{order_id}/groups/{group_id}/report-blocked
{
"account_report_reference": "ЗНАЧЕНИЕ_ИЗ_ОТВЕТА_ЗАЯВКИ"
}
Успех: data.status=quarantined. data.applied=true — это сообщение впервые остановило выдачу; false — учётка уже была приостановлена. Владелец получит уведомление. Повторный запрос сам по себе не создаёт повторного уведомления.
Это кнопка оператора, не автоматическая реакция на таймаут или отсутствие кода. Не отправляйте логин, пароль или код: учётку сервер определяет по выдаче. Нельзя сообщать о чужой или истёкшей заявке.
403 — право не выдано или отключено; 404 — заявка/группа недоступны; 409 — состояние изменилось: обновите заявку и снова попросите подтверждение оператора, не повторяйте автоматически; 410 — заявка истекла. При сетевой ошибке повторяйте прежний запрос с прежним reference. Старые ссылки, оплаты, сроки и право на перевыдачу не изменяются. Возврат учётки в работу доступен только владельцу.
Карантин проверяется и при перевыдаче: заблокированный аккаунт не выбирается даже резервным вариантом. Если подходящей замены нет, API сообщает об отсутствии доступного каталога.
Ошибки и безопасные повторы
| HTTP | Что означает | Что делать |
|---|---|---|
| 400 | Неверные поля | Исправить запрос, не повторять без изменений. |
| 401 | Ключ отключён, ошибочен или IP не разрешён | Проверить основной ключ; по согласованию перейти на резервный. |
| 404 | ID не найден или недоступен партнёру | Обновить каталог и проверить ID. |
| 409 / 410 | Заявка истекла, уже продлена или действие конфликтует | Прочитать текущую заявку и показать понятное состояние оператору. |
| 429 | Лимит запросов или успешных кодов | Для общего лимита ждать Retry-After; лимит кодов не обходить. |
| 500 / 503 | Временная серверная ошибка | Повторить через 2, 5, 15 и 30 секунд с прежними идентификаторами. |
Каждый ответ содержит request_id. Передавайте его владельцу при разборе ошибки, но не прикладывайте ключи, реквизиты и коды.
Чек-лист перед рабочим запуском
- Ключ находится только в серверном секрет-хранилище.
- Одинаковый
external_order_idне создаёт дубль заявки. - Клиентская страница и QR открываются на iPhone.
- Ожидание кода включается до запроса на iPhone.
- Код старше 60 секунд нигде не показывается.
- Реквизиты и коды не попадают в логи и удаляются после заявки.
- Webhook-подпись проверяется, а повтор события безопасен.
- В sandbox долг не увеличивается.
- В рабочем тесте первое нажатие «Установить» создаёт одно начисление за приложение, а повторное нажатие не меняет долг.
Отзыв доступа: только затронутая группа
Блокировка закрывает старые ссылки и ожидания кодов этой Apple-учётки, включая API. История, начисления и рабочие группы сохраняются. Разблокировка не восстанавливает старые отозванные ссылки.
| Ответ | Поля и действие интегратора |
|---|---|
Заявка · groups[] | access_revoked=true, access_revocation_reason=account_blocked, access_message. У приложений группы links=null: не показывать старый URL и не заполнять из кэша. |
/credentials | Отозванная группа сохраняет обязательные login/password как пустые строки и признаки отзыва. HTTP 200 не разрешает вернуть прежний пароль. |
Новый /code-waits | HTTP 409, error.code=access_revoked. Остановить повторы старой попытки и перечитать заявку. |
| Старое ожидание | status=cancelled, при наличии — признаки отзыва. Остановить polling этого ожидания, удалить старый код. Не отключать здоровые группы. |
Подбор замены описан в разделе автозамены. Пока подходящего каталога нет, покажите ожидание у конкретной позиции до прежнего срока. Самостоятельный POST /orders или автоматическая пара set_extend → extend для этого не нужны.
Новые ссылки и пароль без «вечной блокировки»
- На открытии/возврате к карточке, после
order.extendedи при polling получите прежнийGET /orders/{order_id}. Для покупки из нескольких технических заказов обновляйте соответствующие части. - Успешный GET — новый полный снимок этой заявки: атомарно замените группы, ссылки, сроки и статусы. Не дописывайте группы к старым и не используйте
oldRevoked || newRevoked. Блокировка одной группы не блокирует всю заявку. - Продукт сопоставляйте по
groups[].applications[].id; вautomatic_replacement.applications[]и webhook используетсяapplication_id. ID/порядок групп могут измениться. Старые ссылки, реквизиты, коды и ожидания заменённой группы удаляйте. - Доступность вычисляйте заново по текущим признакам отзыва, ссылкам и сроку группы (если он есть), иначе сроку заявки. Успешно заменённая рабочая позиция должна убрать прежний баннер; более поздний новый отзыв снова имеет приоритет.
- Перед показом реквизитов повторно получите
GET /orders/{order_id}/credentials, свяжите по актуальномуgroups[].id. Пароль той же исходной учётки обновляется при чтении без отдельного webhook. Не храните его в БД/кэше/логах и не восстанавливайте старое значение при ошибке или пустой строке. Endpoint ограничен 30 запросами в минуту; учитывайтеRetry-After, не опрашивайте его каждую секунду. - Новое ожидание кода запускается явным действием клиента с новым
group_idи ключом новой попытки. Сетевые повторы этой попытки используют тот жеIdempotency-Key. Старыйwait_idне переносится. - Поздний HTTP-ответ/старый webhook не должен перетирать свежий снимок. Если событие пришло во время обновления, перечитайте ещё раз. Отсутствие необязательного
automatic_replacementв новом ответе не сохраняет старый баннер навсегда.
Существующий order.extended при автозамене содержит automatic=true, reason=account_blocked, limit_consumed=false, automatic_replacement_id, application_id, group_id, expires_at. Это сигнал перечитать заявку, не готовые новые реквизиты. Проверяйте HMAC и X-InstallIOS-Event; повтор события не расходует ручную попытку.
Отдельных webhook о смене пароля и самом отзыве доступа нет. Ограниченный polling и чтение перед показом нужны и при настроенных webhook. Sandbox не выполняет боевую автозамену: блокировку, новый пароль и замену групп проверяйте mock/fixtures, не меняя реальные учётки, оплаты и выдачи.