Перейти к содержимому
API-документация

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
cursornext_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.

statusoperation_statusДеньгиСобытиеЧто делать
processingprocessingВ резерве—Ждать событие или опросить заказ
activesucceededСписаныesim.issuedЧитать профиль
exhaustedsucceededСписаны—Трафик исчерпан. Профиль по-прежнему читается, новых данных нет
expiredsucceededСписаны—Срок действия истёк
failedfailedСняты целикомesim.issue_failed, error_code: provider_rejectedКупить заново с новым ключом и новым external_id
refundedfailedСняты целикомesim.issue_failed, error_code: refundedКупить заново, если eSIM всё ещё нужна
needs_reviewneeds_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.

  1. POST /esim/orders — price_usd переходит из available в резерв в той же транзакции, что и создание заказа. Если available не покрывает сумму, заказа нет: 402 balance_exhausted.
  2. esim.issued — резерв списывается с balance. Операция succeeded. В GET /operations/{id} её net_amount равен "0"; в событии поля net_amount нет.
  3. esim.issue_failed — резерв снимается целиком, balance не меняется.
  4. Возврат оператором — заказ refunded, резерв снимается, приходит esim.issue_failed с error_code: refunded.
  5. 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_idMatching ID, разобран из activation_code. Пусто, если строка другого вида
apnAPN, если провайдер его передал
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КодЗначитПовторять?
404esim_package_not_availableПакет не найден, скрыт или сейчас не может быть выданНет, выбрать пакет из GET /esim/packages
404esim_price_below_costЦена для аккаунта не выше нашей стоимости пакета. Это настройка на нашей сторонеНет, сообщить менеджеру
404tariff_not_availableeSIM не подключён к аккаунтуНет, обратиться к менеджеру
409idempotency_conflictКлюч или external_id уже использован для другого запросаНет. Для новой покупки — новый ключ и новый external_id
402balance_exhaustedavailable не покрывает цену пакетаПосле пополнения счёта
422amount_above_limitЦена пакета выше лимита аккаунта на операциюНет, обратиться к менеджеру
429daily_issue_limitДостигнут лимит покупок или потолок расходов за 24 часаПозже, когда окно освободится, или после повышения лимита
429rate_limitedПревышен бюджет обновления использованияЧерез минуту
502provider_unavailableПровайдер не ответил на обновление использования. Денег это не касаетсяПозже
403forbiddenУ ключа нет нужного scopeНет
404not_foundЗаказа нет в вашем аккаунте или external_id не найденНет
503maintenanceПокупки eSIM закрыты на работы. Резерва нетПозже

Остальные коды — в разделе Ошибки. Неуспешная покупка eSIM завершается одним из кодов: provider_rejected, refunded, needs_review, unknown_order_status.

Справочник