eSIM
Каталог пакетов, покупка eSIM, статусы заказа, резерв денег, профиль установки и события.
Пакеты eSIM покупаются с того же USD-баланса, что и карты, и покупка так же асинхронна. POST /esim/orders отвечает 202 заказом в статусе processing — готовой eSIM в ответе нет. Итог приходит событием esim.issued или esim.issue_failed. Профиль установки (ICCID и код активации) появляется только после того, как деньги списаны.
Доступ
| Scope | Даёт доступ |
|---|---|
esim:read | Каталог, заказы и профиль, обновление использования, операции покупок eSIM, события eSIM и их подтверждение |
esim:write | Покупка (POST /esim/orders). Сам по себе не даёт чтения: результат покупки читается ключом с esim:read |
Ключ с cards:* не видит eSIM, а ключ только с esim:* не видит карты. GET /balance доступен любому ключу.
Каталог
GET /esim/packages — пакеты, доступные вашему аккаунту, с ценой в USD для вас.
| Параметр | Что делает |
|---|---|
country | Двухбуквенный ISO-код: одностранные пакеты этой страны и региональные, куда она входит |
region | Точный region_code, например EUROPE |
limit | Пакетов на страницу: от 1 до 1000, по умолчанию 500 |
cursor | next_cursor предыдущей страницы |
{
"items": [
{
"id": "7d3e1f2a-4b5c-4d6e-8f70-1a2b3c4d5e6f",
"title": "Europe 3 GB, 30 days",
"region_code": "EUROPE",
"region_name": "Europe",
"countries": ["DE", "FR", "IT"],
"data_gb": "3",
"unlimited": false,
"validity_days": 30,
"has_5g": false,
"price_usd": "4.35",
"currency": "USD"
}
],
"next_cursor": "9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
}price_usd— цена вашего аккаунта, её удерживает покупка. Округлена вверх до цента и всегда печатается с двумя знаками:"4.35","5.00".countries— пустой список, если провайдер не опубликовал страны.has_5g—trueтолько если в описании сети упомянут 5G.falseзначит «не указано».- Пока есть
next_cursor, запрашивайте следующую страницу. Если последняя страница заполнена доlimit,next_cursorпридёт, и следующая страница окажется пустой. - Каталог меняется вместе с каталогом провайдеров. Перечитывайте его периодически, а не кэшируйте навсегда.
- Пакета, цена которого для вашего аккаунта не выше нашей стоимости, в списке нет, а его покупка отклоняется
esim_price_below_cost. Это настройка на нашей стороне — сообщите менеджеру. - Если eSIM не подключён к аккаунту, список отвечает
404 tariff_not_available.
Один пакет — GET /esim/packages/{id}: та же запись с ценой для вас. Пакета, которого уже нет в каталоге, нет и здесь: 404 esim_package_not_available.
Покупка
POST /esim/orders держит цену пакета на балансе и запускает выпуск.
package_id— из каталога, обязателен.external_id— ваш номер заказа, необязателен. Уникален в аккаунте, до 128 символов, без пробелов и непечатных знаков. Пустой ни с чем не конфликтует.- Заголовок
Idempotency-Keyобязателен, как и у карт. Ключ создайте один раз на покупку и повторяйте запрос с тем же ключом. Повтор возвращает исходный заказ, а не вторую eSIM.
BODY='{"package_id":"7d3e1f2a-4b5c-4d6e-8f70-1a2b3c4d5e6f","external_id":"shop-order-1042"}'
IDEM=$(uuidgen)
TS=$(date +%s)
SIG=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" "POST" "/api/partner/v1/esim/orders" "$BODY" "$IDEM" \
| openssl dgst -sha256 -hmac "$PP_SECRET" -hex | awk '{print $NF}')
curl -sS -X POST "https://api.plativputi.com/api/partner/v1/esim/orders" \
-H "X-Key-Id: $PP_KEY_ID" -H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
-H "Idempotency-Key: $IDEM" -H "Content-Type: application/json" -d "$BODY"const order = await pp("POST", "/esim/orders",
{ package_id: packageId, external_id: "shop-order-1042" },
{ idempotencyKey: randomUUID() },
);order = pp("POST", "/esim/orders",
body={"package_id": package_id, "external_id": "shop-order-1042"},
idempotency_key=str(uuid.uuid4()))$order = pp('POST', '/esim/orders', ['package_id' => $packageId, 'external_id' => 'shop-order-1042'], uuid4());Ответ — 202 и заказ. Ответ не кэшируется (Cache-Control: no-store).
{
"id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"operation_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "shop-order-1042",
"status": "processing",
"operation_status": "processing",
"price_usd": "4.35",
"created_at": "2026-10-20T10:15:00Z"
}id — наш номер заказа eSIM. operation_id — операция, которая держит ваши деньги; именно её id приходит в событиях.
Повторы
- Тот же ключ, тот же пакет и тот же
external_id— исходный заказ. Это работает, даже если пакет уже ушёл из каталога. - Тот же ключ, но другой пакет или другой
external_id—409 idempotency_conflict. - Новый ключ, но
external_idуже занят —409 idempotency_conflict. Занят значит занят любой операцией, и успешной, и неудачной. Чтобы купить заново после неудачи, берите новыйexternal_id.
Потеряли ответ? Повторите запрос с тем же Idempotency-Key. Если вы передавали external_id, заказ можно найти и так:
const order = await pp("GET", `/esim/orders/external/${externalId}`);order = pp("GET", f"/esim/orders/external/{external_id}")$order = pp('GET', "/esim/orders/external/$externalId");Отказы до резерва
Эти отказы не оставляют ни резерва, ни заказа: 404 esim_package_not_available, 404 esim_price_below_cost, 404 tariff_not_available, 422 amount_above_limit, 503 maintenance, 402 balance_exhausted, 429 daily_issue_limit.
Статусы
Два поля. status — судьба заказа, operation_status — судьба ваших денег. Решая, списано ли, смотрите на operation_status.
status | operation_status | Деньги | Событие | Что делать |
|---|---|---|---|---|
processing | processing | В резерве | — | Ждать событие или опросить заказ |
active | succeeded | Списаны | esim.issued | Читать профиль |
exhausted | succeeded | Списаны | — | Трафик исчерпан. Профиль по-прежнему читается, новых данных нет |
expired | succeeded | Списаны | — | Срок действия истёк |
failed | failed | Сняты целиком | esim.issue_failed, error_code: provider_rejected | Купить заново с новым ключом и новым external_id |
refunded | failed | Сняты целиком | esim.issue_failed, error_code: refunded | Купить заново, если eSIM всё ещё нужна |
needs_review | needs_review | В резерве | operation.needs_review, retry_safe: false | Не покупать заново. Ждать итог: esim.issued или esim.issue_failed |
Выпуск обычно занимает секунды, иногда минуты.
Заказ может показывать active, а operation_status ещё processing — это короткий промежуток между выдачей eSIM и списанием. Профиль в этот момент не отдаётся: он появится вместе с esim.issued.
needs_review значит, что провайдер ответил неоднозначно, и eSIM может существовать. Резерв при этом не снимается автоматически. Итог придёт тем же способом: esim.issued (резерв списан) или esim.issue_failed (резерв снят).
Деньги
Резерв под eSIM работает как у карт (см. Деньги), но проще: отдельных комиссий за eSIM нет, вы платите price_usd.
POST /esim/orders—price_usdпереходит изavailableв резерв в той же транзакции, что и создание заказа. Еслиavailableне покрывает сумму, заказа нет:402 balance_exhausted.esim.issued— резерв списывается сbalance. Операцияsucceeded. ВGET /operations/{id}еёnet_amountравен"0"; в событии поляnet_amountнет.esim.issue_failed— резерв снимается целиком,balanceне меняется.- Возврат оператором — заказ
refunded, резерв снимается, приходитesim.issue_failedсerror_code: refunded. needs_review— резерв держится до разбора. Потом он списывается (esim.issued) или снимается (esim.issue_failed).
Цена и лимиты
Цена покупки — price_usd из каталога для вашего аккаунта. Отдельного POST /quote для eSIM нет: цена уже видна в GET /esim/packages и в заказе.
Лимиты аккаунта распространяются и на eSIM. amount_above_limit — на цену одного пакета. daily_issue_limit — на покупки за скользящие 24 часа: неудачные покупки считаются тоже. Если задан дневной потолок расходов на eSIM в USD, действует и он. Значения задаёт ваш менеджер.
Профиль установки
Профиль — это то, что нужно для установки eSIM на устройство. Он появляется в GET /esim/orders/{id}, в GET /esim/orders/external/{external_id} и в ответе обновления использования, только когда выполнены оба условия:
operation_status—succeeded, то есть резерв списан;status—active,exhaustedилиexpired.
Профиля нет в событиях, в очереди и в GET /operations/{id}: событие несёт esim_order_id, а сам профиль читается по заказу. Если операция снята, профиль не отдаётся, даже когда eSIM уже выдана (поздняя доставка после возврата резерва).
| Поле | Что это |
|---|---|
iccid | Идентификатор SIM-профиля |
activation_code | Полная строка LPA вида LPA:1$<SM-DP+>$<matching id> |
smdp_address | Адрес SM-DP+, разобран из activation_code. Пусто, если строка другого вида |
matching_id | Matching ID, разобран из activation_code. Пусто, если строка другого вида |
apn | APN, если провайдер его передал |
qr_payload | Строка для QR-кода установки, совпадает с activation_code. Картинку QR рисуйте сами |
{
"id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"operation_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "shop-order-1042",
"status": "active",
"operation_status": "succeeded",
"price_usd": "4.35",
"profile": {
"iccid": "8944000000000000000",
"activation_code": "LPA:1$smdp.example.net$EXAMPLE-MATCHING-ID",
"smdp_address": "smdp.example.net",
"matching_id": "EXAMPLE-MATCHING-ID",
"qr_payload": "LPA:1$smdp.example.net$EXAMPLE-MATCHING-ID"
},
"usage": {
"data_total_gb": "3",
"data_remaining_gb": "2.7",
"unlimited": false,
"activated_at": "2026-10-20T10:17:00Z",
"expires_at": "2026-11-19T10:17:00Z"
},
"created_at": "2026-10-20T10:15:00Z",
"settled_at": "2026-10-20T10:15:41Z"
}Профиль — учётные данные
Кто получил activation_code, тот может установить eSIM. Не логируйте его и не храните открытым текстом. Ответы заказа не кэшируются.
Использование
Объект usage — последнее известное состояние. data_total_gb не отдаётся для безлимитных пакетов. data_remaining_gb — последнее показание.
POST /esim/orders/{id}/usage-refresh спрашивает провайдера о текущем расходе и возвращает заказ с полем refreshed.
- Не чаще одного раза в минуту на заказ. Внутри минуты отдаётся сохранённое показание с
refreshed: false. refreshed: falseи тогда, когда спрашивать не о чем: у заказа ещё нет активного профиля.- Бюджет на ключ отдельный: 30 обращений в минуту на все заказы вместе, или общий лимит ключа, если он меньше. Сверх этого —
429 rate_limited. - Сбой провайдера —
502 provider_unavailable. Денег он не касается, повторите позже. - Денег запрос не двигает,
Idempotency-Keyне нужен.
TS=$(date +%s)
SIG=$(printf '%s\n%s\n%s\n%s' "$TS" "POST" "/api/partner/v1/esim/orders/$ORDER/usage-refresh" "" \
| openssl dgst -sha256 -hmac "$PP_SECRET" -hex | awk '{print $NF}')
curl -sS -X POST "https://api.plativputi.com/api/partner/v1/esim/orders/$ORDER/usage-refresh" \
-H "X-Key-Id: $PP_KEY_ID" -H "X-Timestamp: $TS" -H "X-Signature: $SIG"События
type | Когда |
|---|---|
esim.issued | Профиль выдан, резерв списан |
esim.issue_failed | Покупка не состоялась, резерв снят. error_code: provider_rejected или refunded |
operation.needs_review | Нужен ручной разбор, резерв держится. Не покупайте заново |
{
"id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
"type": "esim.issued",
"operation_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
"payload": {
"operation_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
"kind": "esim_purchase",
"status": "succeeded",
"esim_order_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"external_id": "shop-order-1042"
},
"created_at": "2026-10-20T10:15:41Z"
}В payload нет профиля и нет номера карты. Чтобы получить профиль, читайте заказ по esim_order_id или external_id. esim.issue_failed несёт status: failed и error_code. operation.needs_review несёт status: needs_review, error_code (needs_review или unknown_order_status) и retry_safe: false. Событие может прийти повторно: обрабатывайте его идемпотентно по operation_id, как и события карт.
Какие события видит ключ
Очередь фильтруется по scope ключа. esim.* и operation.* операций eSIM видит ключ с esim:read. Подтверждение чужого события — 403 forbidden.
Вебхуки устроены иначе. Подписка принадлежит аккаунту: создавать, менять и удалять её может ключ с cards:write, смотреть — с cards:read. Доставка фильтруется только по event_types, а не по scope ключа. Подписка без event_types получает и события eSIM. Если вы продаёте только eSIM, укажите в event_types события eSIM или читайте очередь.
GET /operations/{id} для покупки eSIM требует esim:read. В ответе kind: esim_purchase, а также esim_order_id и external_id.
Коды ошибок eSIM
| HTTP | Код | Значит | Повторять? |
|---|---|---|---|
| 404 | esim_package_not_available | Пакет не найден, скрыт или сейчас не может быть выдан | Нет, выбрать пакет из GET /esim/packages |
| 404 | esim_price_below_cost | Цена для аккаунта не выше нашей стоимости пакета. Это настройка на нашей стороне | Нет, сообщить менеджеру |
| 404 | tariff_not_available | eSIM не подключён к аккаунту | Нет, обратиться к менеджеру |
| 409 | idempotency_conflict | Ключ или external_id уже использован для другого запроса | Нет. Для новой покупки — новый ключ и новый external_id |
| 402 | balance_exhausted | available не покрывает цену пакета | После пополнения счёта |
| 422 | amount_above_limit | Цена пакета выше лимита аккаунта на операцию | Нет, обратиться к менеджеру |
| 429 | daily_issue_limit | Достигнут лимит покупок или потолок расходов за 24 часа | Позже, когда окно освободится, или после повышения лимита |
| 429 | rate_limited | Превышен бюджет обновления использования | Через минуту |
| 502 | provider_unavailable | Провайдер не ответил на обновление использования. Денег это не касается | Позже |
| 403 | forbidden | У ключа нет нужного scope | Нет |
| 404 | not_found | Заказа нет в вашем аккаунте или external_id не найден | Нет |
| 503 | maintenance | Покупки eSIM закрыты на работы. Резерва нет | Позже |
Остальные коды — в разделе Ошибки. Неуспешная покупка eSIM завершается одним из кодов: provider_rejected, refunded, needs_review, unknown_order_status.