Tổng Kho eSIM
OpenAPI 2.4.0 · операций: 30

Справочник API

Все методы /v1. Страница формируется из спецификации OpenAPI, созданной при сборке сервера: пути, параметры, схемы, перечисления и ошибки соответствуют поведению сервера.

Рабочий режим

https://api.tongkhoesim.com/v1

Тестовый режим

https://api-testing.tongkhoesim.com/v1

Рабочее окружение: api.tongkhoesim.com / tke_live_ (партнёры) или tke_member_live_ (одобренные участники). Тестовое: api-testing.tongkhoesim.com / tke_test_ / tke_member_test_ (партнёры / участники).

Общие правила API

Следующие правила (6) действуют для всех операций (30) и не повторяются в описаниях отдельных методов.

Аутентификация

Каждый метод /v1, включая GET, требует подписи HMAC-SHA256 с 4 заголовками: X-TKE-Key · X-TKE-Timestamp · X-TKE-Nonce · X-TKE-Signature. Обхода аутентификации нет.

Формат ответа
Успех: { ok: true, data }, ошибка: { ok: false, error: { code, message, field?, meta?, request_id } } с соответствующим HTTP-статусом. Ошибки не возвращаются с 200; если достаточно статуса, не определяйте результат по телу.
Типы данных
Деньги — целое число VND (BIGINT, без дробей и символов валюты). Объём данных всегда в байтах, включая каталог (data.total_bytes / data.daily_bytes). Время — ISO-8601 UTC; преобразуйте часовой пояс при отображении.
Ограничение частоты
Ответы 429 содержат Retry-After и X-RateLimit-Limit / Remaining / Reset. Читайте заголовки: лимиты настраиваются для каждого ключа.
request_id
Каждый ответ содержит X-Request-Id; при ошибке он также находится в error.request_id. Записывайте значение в журнал: оно позволяет поддержке найти конкретный запрос на сервере.
Idempotency-Key

Одинаковые ключ и тело в течение 24 часов возвращают исходный результат без повторного списания. Область действия — api_key_id. Обязателен для 5 операций: POST /v1/orders, POST /v1/orders/bulk, POST /v1/esims/{id}/cancel, POST /v1/esims/{id}/change-plan, POST /v1/esims/{id}/refund-requests.

Два вида пагинации

Используйте отдельную обработку: форматы ответов и условия завершения различаются.

Смещение · page / page_size

Используется только в GET /v1/webhooks/deliveries. Возвращает page, page_size, total.

За пределами списка возвращаются items: [] и фактический total с HTTP 200, не 404. Остановитесь при пустом items или после обработки total элементов.

Курсор · cursor / next_cursor

Используется в GET /v1/catalog/packages, GET /v1/orders, GET /v1/esims и GET /v1/esims/{id}/plan-options. Заказы и eSIM поддерживают сверку через updated_since; каталог и варианты тарифов — нет.

Остановитесь, если next_cursor ОТСУТСТВУЕТ или РАВЕН текущему курсору. Поле может полностью отсутствовать в data (тип string | null | undefined). Без этой проверки возможен бесконечный цикл.

Почему неверный курсор возвращает HTTP 200

Неверный курсор не вызывает ошибку: сервер возвращается к началу списка, сохраняя совместимость клиентов при смене кодирования. Поэтому цикл с проверкой только next_cursor !== null может бесконечно читать первую страницу.

javascript
let cursor = null;
const seen = new Set();

while (true) {
  const params = new URLSearchParams({ page_size: "200" });
  if (cursor) params.set("cursor", cursor);

  // callSigned — ваша функция, добавляющая 4 заголовков X-TKE-* (см. страницу HMAC).
  const page = await callSigned("GET", `/v1/orders?${params}`);
  for (const order of page.data.items) await handle(order);

  const next = page.data.next_cursor;
  if (!next || next === cursor || seen.has(next)) break;
  seen.add(next);
  cursor = next;
}

Каталог

Каталог тарифов: ЕДИНЫЙ список для двух систем поставки. Система — поле `market` (`domestic` | `international`) и фильтр `?market=`, а не отдельные пути. Данных о запасе НЕТ: ни числа, ни уровней `available`/`low`/`out_of_stock`. Если поставщик не может выдать тариф, POST /v1/orders вернёт явную ошибку `esim.out_of_stock` и подобную. Единая курсорная пагинация. `total` заполняется при дешёвом подсчёте (внутренняя система), иначе `null`.

GET/v1/catalog/packages

Список тарифов

scope catalog:readget_catalog_packages

Требуемая область доступа: catalog:read.

Каталог организации: ЕДИНЫЙ список для двух систем поставки с фильтром ?market=. Возвращаются только реально доступные для ПОКУПКИ тарифы: опубликованные, поставляемые, разрешённые ключу и с ценой организации. Курсорная пагинация: первый запрос без cursor, затем передавайте next_cursor до null. Короткая страница НЕ означает конец. capabilities берётся из РЕАЛЬНОЙ декларации тарифа; при отсутствии декларации все флаги false, чтобы не обещать вызов с неизбежным 422. В каталоге НЕТ данных о запасе. Если поставщик не может выдать тариф, POST /v1/orders вернёт явную ошибку esim.out_of_stock и подобную при заказе.

Параметры запроса (18)

page_sizeintegerнеобязательнопо умолчанию 50min 1max 100

Записей на странице. По умолчанию 50, максимум 100. Большее значение ограничивается 100, это НЕ ошибка.

marketstringнеобязательно

Система поставки: domestic | international. Значение вне списка даёт internal.invalid_filter (400), а не весь каталог: опечатка не должна отключать фильтр.

countrystringнеобязательно

Код страны ISO-3166 alpha-2, например VN. Только тарифы с покрытием этой страны.

regionstringнеобязательно

Код региона, например chau-a. Тарифы с покрытием хотя бы одной страны региона и все глобальные тарифы.

qstringнеобязательно

Поиск по публичному коду и названию тарифа: частичное совпадение без учёта регистра.

tagstringнеобязательно

Фильтр по ОДНОЙ коммерческой метке. Нормализация как при сохранении: лишние пробелы удаляются, нижний регистр, дефисы; Ban Le и ban-le — одна метка.

data_modelstringнеобязательно

Модель данных: daily | fixed | unlimited. ЗАМЕНЯЕТ старое supports_topup. Неизвестное значение: internal.invalid_filter (400).

data_min_bytesinteger · int64необязательно

Минимальный лимит в БАЙТАХ. Дневные тарифы сравниваются по ДНЕВНОМУ лимиту; безлимитные ВСЕГДА проходят нижнюю границу.

data_max_bytesinteger · int64необязательно

Максимальный лимит в БАЙТАХ. Безлимитные тарифы ИСКЛЮЧАЮТСЯ: верхняя граница данных к ним неприменима и не должна выдавать безлимит ищущему малый пакет.

days_mininteger · int64необязательно

Минимальный срок в днях.

days_maxinteger · int64необязательно

Максимальный срок в днях.

price_mininteger · int64необязательно

Минимальная цена VND по цене ВАШЕЙ ОРГАНИЗАЦИИ. Фильтр применяется ПОСЛЕ формирования страницы, поэтому короткая страница не означает конец данных.

price_maxinteger · int64необязательно

Максимальная цена VND, правила как у price_min.

network_genstringнеобязательно

Только тарифы с поддержкой поколения сети 2g | 3g | 4g | 5g. Неизвестное значение: internal.invalid_filter (400).

hotspotstringнеобязательно
Допустимые значения:truefalse

true — только тарифы с ЗАЯВЛЕННОЙ раздачей Wi-Fi; false — с ЗАЯВЛЕННЫМ запретом. Без декларации (features.hotspot = null) тариф не проходит НИ ОДИН вариант. Допустимы только строки true/false.

appstringнеобязательно

Только тарифы с ЗАЯВЛЕННОЙ поддержкой приложения хотя бы на одной ОС (app=tiktok). Отсутствие сведений не означает поддержку. Пока принимается только tiktok; другой код: internal.invalid_filter (400).

voicestringнеобязательно
Допустимые значения:truefalse

true — только тарифы с ЗАЯВЛЕННЫМ голосом; false — с ЗАЯВЛЕННЫМ отсутствием. Без декларации тариф не проходит ни один вариант. Допустимы только строки true/false.

cursorstringнеобязательно

Передайте next_cursor предыдущей страницы БЕЗ ИЗМЕНЕНИЙ. Остановитесь при next_cursor = null, а НЕ при короткой странице. Ограничения каталога ключа, наличие цены организации и фильтр цены применяются ПОСЛЕ разбиения: за страницей из 3 элементов могут оставаться тысячи тарифов. Курсор содержит ОТПЕЧАТОК фильтров. Другие фильтры или повреждённый курсор дают catalog.cursor_mismatch (400), чтобы не смешивать результаты разных запросов.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
itemsobject[]
—
capabilitiesobject

Доступные действия ПОСЛЕ продажи из РЕАЛЬНОЙ декларации тарифа, без предположений по типу поставщика. Без декларации все флаги false, чтобы не обещать метод с неизбежным 422 capability.not_supported.

cancelobject
—
restrictionstring
допускает null

code_not_disclosed — отмена возможна, пока НИКТО не видел код активации: не отправлен клиенту и не прочитан через панель или ?include=lpa. Это РЕАЛЬНОЕ условие отмены. Оно ничего не говорит об установке клиентом, которую система не может знать; прежнее unused_only ошибочно обещало обратное.

code_not_disclosednull
supportedboolean
—
plan_changeobject
—
policyobject
допускает null

Политика ДОБАВЛЕНИЯ НОВОГО ТАРИФА поставщика. null — тариф нельзя пополнить ИЛИ политика ещё не проверена. Это означает «не делайте предположений», а не объект с false, который обещал бы отсутствие отмены.

……

Вложенные поля смотрите в примере JSON.

supportedboolean
—
usage_checkobject
—
supportedboolean
—
codestring

Код тарифа ВАШЕГО каталога, public_sku. НЕИЗМЕНЯЕМ для тарифа даже при смене поставщика, подходит для сверки. Пример имеет правильный ФОРМАТ, но не работает: получите реальный код из GET /v1/catalog/packages.

coverageobject

Где работает тариф.

countriesstring[]

Коды ISO-3166 alpha-2. Пустой массив у глобальных тарифов (200 стран не перечисляются) и тарифов без заявленного покрытия.

country_countinteger

Количество стран покрытия для сортировки или фильтрации многострановых тарифов без загрузки и подсчёта всего массива.

groupstring

Тип покрытия: страны country, регион region или весь мир global. ОТКРЫТОЕ перечисление: игнорируйте неизвестные значения.

regionstring
допускает null

Пока ВСЕГДА null: регионы связаны со СТРАНАМИ, а не тарифами. Значение было бы предположением. Фильтр ?region= продолжает работать.

dataobject

Лимит данных тарифа. ВСЕ значения в БАЙТАХ, не МБ/ГБ, чтобы избежать двойного округления.

daily_bytesinteger · int64
допускает null

ДНЕВНОЙ лимит данных в байтах. null для fixed/unlimited.

labelstring

Понятная пользователю подпись, созданная общей для всех экранов функцией.

modelstring

Учёт лимита: daily (на день) | fixed (на весь срок) | unlimited. ОТКРЫТОЕ перечисление: клиент ОБЯЗАН игнорировать неизвестные значения.

dailyfixedunlimited
resetobject
допускает null

Момент сброса дневного лимита. Неизвестное время может лишить купившего в 22:00 почти всего первого дня. null — не заявлено.

atstring
допускает null

Местное время сброса в часовом поясе timezone (00:00, 00:00:01).

notestring
допускает null
—
timezonestring
допускает null

Часовой пояс сброса (UTC+8, UTC+7).

total_bytesinteger · int64
допускает null

Общий лимит на весь срок в байтах. null для daily/unlimited: произведение дневного лимита на дни не является обязательством поставщика.

descriptionstring
допускает null

Описание тарифа для продажи. null, если ещё не написано.

description_enstring
допускает null

Описание на английском. null, если ещё не задано.

featuresobject

Возможности тарифа. ТИПИЗИРОВАННЫЙ объект: массив [] не различает отсутствие раздачи и отсутствие сведений, а false и null различают.

appsobject[]

Поддержка приложения по ОПЕРАЦИОННЫМ СИСТЕМАМ. Отсутствие приложения в массиве означает ОТСУТСТВИЕ ДАННЫХ, а не поддержку. Поставщик гарантирует только перечисленное; пока заявлен только TikTok. Объединённого флага нет намеренно: в реальном каталоге 2465 тарифов поддерживают TikTok на iPhone, но не Android. Единый флаг исказил бы почти треть каталога.

androidstring

supported — поставщик заявляет поддержку; blocked — запрет; unknown — нет декларации для этой ОС. ОТКРЫТОЕ перечисление.

supportedblockedunknown
appstring

Код приложения в нижнем регистре, например tiktok.

iosstring

supported — поставщик заявляет поддержку; blocked — запрет; unknown — нет декларации для этой ОС. ОТКРЫТОЕ перечисление.

supportedblockedunknown
notestring
допускает null
—
hotspotboolean
допускает null

Раздача Wi-Fi другим устройствам. null — не заявлено и ОТЛИЧАЕТСЯ от false.

kyc_requiredboolean
допускает null

Перед использованием требуется проверка личности KYC. null — не заявлено.

smsboolean
допускает null

SMS. false — сообщения НЕ принимаются, в том числе банковские OTP. null — не заявлено.

voiceboolean
допускает null

Голосовые вызовы. false — поставщик заявляет ОТСУТСТВИЕ голоса: линия только данных не принимает вызовы. null — не заявлено.

fupobject
допускает null

Поведение после исчерпания скоростного лимита. null — сведения об ограничении не заявлены.

behaviorstring
допускает null

throttle — замедление; cutoff — отключение; null — не заявлено. ОТКРЫТОЕ перечисление.

throttlecutoffnull
high_speed_bytesinteger · int64
допускает null

Объём в БАЙТАХ на полной скорости до ограничения.

notestring
допускает null
—
speed_kbpsinteger
допускает null

Скорость ПОСЛЕ исчерпания скоростного лимита, кбит/с. null при отключении или отсутствии декларации. ОТЛИЧАЕТСЯ от network.speed_cap_kbps, ограничивающего весь тариф.

marketstring

СИСТЕМА ПОСТАВКИ, ОТКРЫТОЕ перечисление: игнорируйте неизвестные значения. Определяет запас и действия после продажи: domestic продаёт комплекты с пополнением, отменой и проверкой расхода; international заказывается у поставщика по запросу без запаса.

domesticinternational
namestring
—
name_enstring
допускает null

Название на английском. null, если ещё не задано.

networkobject

Сети, в которых работает тариф.

apnstring
допускает null

APN для ручного ввода на Android, если устройство не подхватило его автоматически. Требуется для инструкции установки, иначе клиенту понадобится поддержка. null — не заявлено.

carriersobject[]

Операторы связи, В СЕТЯХ которых работает тариф. Характеристика eSIM, а не поставщик.

namestring
—
generationsstring[]
допускает null

Доступные поколения сети (["4g", "5g"]). null — не заявлено, а НЕ «только 2G».

speed_cap_kbpsinteger
допускает null

Ограничение скорости ВСЕГО тарифа, кбит/с, в отличие от fup.speed_kbps после исчерпания скоростного лимита.

priceobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
tagsstring[]

Нормализованные коммерческие метки: нижний регистр, дефисы. По ним также работают ограничения каталога ключа. Пока меток у тарифов нет, ?tag= возвращает пустой список до начала их назначения продавцом.

validityobject

Срок действия тарифа и момент начала отсчёта.

daysinteger
—
install_by_daysinteger
допускает null

Число дней после покупки для сканирования QR и установки eSIM. null — не заявлено, а НЕ «устанавливайте когда угодно».

labelstring
—
reclaim_on_expiryboolean
допускает null

Поставщик ИЗЫМАЕТ подписку после истечения тарифа: пополнение и смена становятся невозможны. Важный срок при продаже продлений. null — не заявлено.

starts_whenstring
допускает null

КОГДА начинается срок: on_install (установка eSIM клиентом) | on_purchase (покупка) | null. Критически важное поле: незнание отсчёта от УСТАНОВКИ приведёт к неверному обещанию срока каждому клиенту. При null не обещайте точную дату.

on_installon_purchasenull
unused_valid_daysinteger
допускает null

Число дней хранения неиспользованного тарифа до истечения срока. null — не заявлено, НЕ бессрочно. Отдельно от install_by_days: одно — срок УСТАНОВКИ, другое — ХРАНЕНИЯ.

modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_cursorstring
допускает null

Передайте БЕЗ ИЗМЕНЕНИЙ для следующей страницы. null — БОЛЬШЕ ДАННЫХ НЕТ, единственный сигнал остановки: за страницей короче page_size могут оставаться другие.

page_sizeinteger
допускает null

МАКСИМАЛЬНОЕ число записей, использованное сервером для страницы с учётом ограничения, а не фактическая длина items.

totalinteger · int64
допускает null

Общее число тарифов по фильтрам. null при дорогом подсчёте (международная система или без market). Подсчёт идёт ДО ограничений каталога ключа и наличия цены организации: это число совпадений в каталоге, а не доступных вам покупок.

warningsobject[]

ПРИЧИНЫ состава списка. Пусто — особенностей нет. code — ЗАКРЫТЫЙ набор: filtered_by_key_package_filters (ограничения каталога ключа), filtered_by_provider_scope (поставщики организации), no_price_for_org (нет цены), price_filter_removed_rows (фильтр цены), provider_disabled (нет доступного поставщика), capability_missing (не заявлены действия после продажи). count — число затронутых строк или null, если не выразимо числом.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
допускает null
—
messagestring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failedinternal.invalid_filtercatalog.cursor_mismatch
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="GET"
REQ_PATH="/v1/catalog/packages"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "items": [
      {
        "capabilities": {
          "cancel": {
            "restriction": "code_not_disclosed",
            "supported": true
          },
          "plan_change": {
            "policy": {
              "note": "<note>",
              "requires_cancel_first": true,
              "stacking": true
            },
            "supported": true
          },
          "usage_check": {
            "supported": true
          }
        },
        "code": "TKE-VN-30D-X3K9F",
        "coverage": {
          "countries": [
            "CN"
          ],
          "country_count": 1,
          "group": "country",
          "region": "<region>"
        },
        "data": {
          "daily_bytes": 1073741824,
          "label": "1GB/ngày",
          "model": "daily",
          "reset": {
            "at": "00:00",
            "note": "<note>",
            "timezone": "UTC+8"
          },
          "total_bytes": null
        },
        "description": "<description>",
        "description_en": "<description_en>",
        "features": {
          "apps": [
            {
              "android": "blocked",
              "app": "tiktok",
              "ios": "supported",
              "note": "<note>"
            }
          ],
          "hotspot": true,
          "kyc_required": false,
          "sms": false,
          "voice": false
        },
        "fup": {
          "behavior": "throttle",
          "high_speed_bytes": 1048576,
          "note": "<note>",
          "speed_kbps": 128
        },
        "market": "international",
        "name": "eSIM Trung Quốc Mainland Multi - 1GB/ngày - 24 ngày",
        "name_en": "<name_en>",
        "network": {
          "apn": "e-ideas",
          "carriers": [
            {
              "name": "China Unicom"
            }
          ],
          "generations": [
            "4g",
            "5g"
          ],
          "speed_cap_kbps": 1
        },
        "price": {
          "amount": 273000,
          "currency": "VND"
        },
        "tags": [],
        "validity": {
          "days": 24,
          "install_by_days": 365,
          "label": "24 ngày",
          "reclaim_on_expiry": true,
          "starts_when": "on_install",
          "unused_valid_days": 1
        }
      }
    ],
    "mode": "live",
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNy0xMVQwODoxMjozMFoifQ",
    "page_size": 50,
    "total": 128,
    "warnings": []
  },
  "ok": true
}
GET/v1/catalog/packages/{code}

Получить один тариф

scope catalog:readget_catalog_packages__code_

Требуемая область доступа: catalog:read.

Детали ОДНОГО тарифа по публичному коду. data содержит ТЕ ЖЕ ПОЛЯ, что элемент GET /v1/catalog/packages, плюс mode. Для Package достаточно одного типа. Недоступный код даёт partner.package_not_visible (404). Один код ошибки для отсутствующего, запрещённого ключу, не имеющего цены организации или потерявшего поставщика тарифа: различение позволило бы постороннему перечислить весь каталог.

Параметры пути (1)

codestringобязательно

Код тарифа из каталога — code (public_sku). НЕИЗМЕНЯЕМ при смене поставщика, подходит для сверки. Запрещённый ключу или не имеющий цены организации код даёт 404 partner.package_not_visible, как несуществующий.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
partner.package_not_visible
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
CODE="THAY-BANG-CODE"

METHOD="GET"
REQ_PATH="/v1/catalog/packages/$CODE"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": null,
  "ok": true
}

Заказы

Создание заказов и сверка

GET/v1/orders

Список заказов (сверка запросами)

scope orders:readget_orders

Требуемая область доступа: orders:read.

Параметры запроса (5)

page_sizeintegerнеобязательнопо умолчанию 50min 1max 200

Записей на странице. По умолчанию 50, максимум 200. Большее значение ограничивается 200, это НЕ ошибка.

statusstringнеобязательно
Допустимые значения:completedpending_providerprovider_failedcancelled

Фильтр по статусу заказа. Значение вне списка: internal.invalid_filter (400).

external_refstringнеобязательно

Фильтр по вашей ссылке, переданной при оформлении (ТОЧНОЕ совпадение).

updated_sincestring · date-timeнеобязательно

ISO-8601 UTC. Записи с updated_at >= updated_since по возрастанию — стандартная СВЕРКА ЗАПРОСАМИ (§8.5), позволяющая восстановить пропущенный вебхук по состоянию. Неверный формат: internal.invalid_filter (400).

cursorstringнеобязательно

Курсор следующей страницы: передайте next_cursor предыдущей страницы БЕЗ ИЗМЕНЕНИЙ. ВНИМАНИЕ: неверный курсор не вызывает ошибку, сервер снова читает НАЧАЛО списка для совместимости при смене кодирования. Поэтому перед повтором проверяйте, что next_cursor отличается от отправленного курсора, иначе цикл будет бесконечно читать первую страницу.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
itemsobject[]
—
created_atstring · date-time
—
external_refstring
допускает null
—
fulfillment_statusstring
—
fulfilledpartially_fulfilledunfulfilled
idstring · uuid
—
item_countinteger · int64
—
netobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
public_refstring
допускает null
—
refundedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
statusstring
—
totalobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
updated_atstring · date-time
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_cursorstring
допускает null

Передайте БЕЗ ИЗМЕНЕНИЙ для следующей страницы. null — БОЛЬШЕ ДАННЫХ НЕТ, единственный сигнал остановки: за страницей короче page_size могут оставаться другие.

page_sizeinteger
допускает null

МАКСИМАЛЬНОЕ число записей, использованное сервером для страницы с учётом ограничения, а не фактическая длина items.

totalinteger · int64
допускает null

Число строк по фильтру. null, если подсчёт слишком дорог. Конец списка определяйте по next_cursor, а не по этому полю.

warningsobject[]

ПРИЧИНЫ состава списка. Пусто — особенностей нет. code — ЗАКРЫТЫЙ набор: filtered_by_key_package_filters (ограничения каталога ключа), filtered_by_provider_scope (поставщики организации), no_price_for_org (нет цены), price_filter_removed_rows (фильтр цены), provider_disabled (нет доступного поставщика), capability_missing (не заявлены действия после продажи). count — число затронутых строк или null, если не выразимо числом.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
допускает null
—
messagestring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failedinternal.invalid_filter
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="GET"
REQ_PATH="/v1/orders"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "items": [
      {
        "created_at": "2026-07-11T08:12:30Z",
        "external_ref": "po-1001",
        "fulfillment_status": "fulfilled",
        "id": "018f2b00-0000-7000-8000-00000000009a",
        "item_count": 2,
        "net": {
          "amount": 273000,
          "currency": "VND"
        },
        "public_ref": "TKE-DH-2A4C7Q",
        "refunded": {
          "amount": 273000,
          "currency": "VND"
        },
        "status": "completed",
        "total": {
          "amount": 273000,
          "currency": "VND"
        },
        "updated_at": "2026-07-11T09:03:11Z"
      }
    ],
    "mode": "live",
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNy0xMVQwODoxMjozMFoifQ",
    "page_size": 50,
    "total": 128,
    "warnings": []
  },
  "ok": true
}
POST/v1/orders

Создать заказ (прямая покупка)

scope orders:writeТребуется Idempotency-Keypost_orders

Требуемая область доступа: orders:write.

Заголовки (1)

Idempotency-Keystringобязательно

Строка клиента: одинаковые ключ и тело выполняются ровно один раз; повтор возвращает исходный результат. Область действия — api_key_id (§11): разные ключи означают разные команды.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
external_refstring
допускает null

Ваша ссылка, повторяется в ответе и вебхуках.

includestring[]
допускает null

Дополнительные блоки: ["lpa"] или ["lpa","qr"]. Если не указаны, items[].install равен null, и ВСЕ eSIM заказа сохраняют право на отмену. ВОЗВРАТ КОДА СЧИТАЕТСЯ РАСКРЫТИЕМ: запрос lpa закрывает возможность отмены уже при оформлении заказа.

invoice_buyerobject
допускает null

Только ключи участников. Без данных используется потребительский сценарий без организации или юридического лица во Вьетнаме. Переданные данные проверяются по текущим правилам: full_name, 12-значный персональный tax_code и address/email при оформлении. Корпоративный счёт запрашивается после покупки. Тестовый режим только проверяет снимок данных и не выпускает счета MISA.

addressstring
допускает null
—
emailstring
допускает null
—
full_namestring
допускает null
—
legal_namestring
допускает null
—
tax_codestring
допускает null
—
itemsobject[]
обязательно

Список тарифов и количества для покупки (не более 50 eSIM).

package_codestring
обязательно

Код тарифа ВАШЕГО каталога, public_sku. НЕИЗМЕНЯЕМ для тарифа даже при смене поставщика, подходит для сверки. Пример имеет правильный ФОРМАТ, но не работает: получите реальный код из GET /v1/catalog/packages.

quantityinteger
обязательно

Количество eSIM этого тарифа для покупки (≥ 1).

Ответ

200Повторный ответ по оформленному заказу

Повтор ранее выполненной команды: те же Idempotency-Key и тело за 24 часа. Тело ответа ИДЕНТИЧНО первому, повторного списания НЕТ, добавляется Idempotent-Replayed: true. НОВАЯ операция возвращает 201; не определяйте успех только по этому статусу.

ПолеТипОграниченияОписание
dataobject
обязательно

Заказ партнёра.

created_atstring · date-time
—
external_refstring
допускает null
—
fulfillment_statusstring

Фактически выданное количество — ОТДЕЛЬНО от status. Заказ из 3 eSIM с 2 выданными и 1 ошибкой остаётся status=completed, но имеет fulfillment_status=partially_fulfilled и refunded_amount > 0.

fulfilledpartially_fulfilledunfulfilled
idstring · uuid

ID заказа UUID.

idempotent_replayboolean
допускает null

true при идемпотентном повторе предыдущего успешного запроса.

invoice_nostring
допускает null

Номер счёта после выпуска.

invoice_statusstring

Статус счёта с НДС заказа. none — ещё не выпущен.

nonependingissuingissuedfailedcancelled
itemsobject[]
—
cancellableobject

Можно ли ещё отменить eSIM и почему нельзя. Прочитайте ДО POST /v1/esims/{order_item_id}/cancel. Блок присутствует ВСЕГДА и никогда не null.

allowedboolean
—
disclosed_atstring · date-time
допускает null

Время выдачи кода за пределы системы (ISO-8601 UTC), null, пока его никто не видел. Позволяет проверить жалобу «я не получил код, почему нельзя отменить».

reasonstring
допускает null

Причина запрета отмены: code_disclosed — код выдан или показан; wrong_status — неподходящее состояние; expired — срок истёк; not_supported — отмена не заявлена тарифом. null при allowed = true. Термины как в meta.reason ошибки esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
допускает null

Ваша ссылка заказа external_ref, переданная при покупке, для связи eSIM с вашей системой. null, если не указана.

iccidstring
допускает null
—
idstring · uuid

ID eSIM для всех путей /v1/esims/{id}/…, он же order_item_id вебхуков. Если известен только ICCID, получите id через GET /v1/esims?iccid=….

installobject
допускает null

Данные для УСТАНОВКИ eSIM. null означает, что вы их НЕ ЗАПРАШИВАЛИ (нет include=lpa), а не отсутствие кода. Если кода ещё нет, блок присутствует с lpa: null. Ответ с этим блоком всегда имеет Cache-Control: no-store.

android_urlstring
допускает null
—
apnstring
допускает null

APN для ручного ввода, если устройство не получает настройки автоматически. В текущей версии ВСЕГДА null, что означает «неизвестно», а не «не требуется».

ios_urlstring
допускает null
—
lpastring
допускает null

Код активации eSIM — секрет продажи. Этой строки достаточно для создания QR-кода у вас.

matching_idstring
допускает null
—
qr_code_base64string
допускает null

PNG data-URI QR-кода. Только при include=qr в ДЕТАЛЬНОМ методе. В списках его НЕТ (около 11 КБ на строку, то есть 1,1 МБ на 100 строк); создавайте QR из lpa.

smdp_addressstring
допускает null
—
msisdnstring
допускает null
—
order_idstring · uuid
—
package_codestring
допускает null

КОД тарифа из каталога для связи eSIM с вашим каталогом. null только если исходный тариф удалён из каталога.

package_namestring

Отображаемое НАЗВАНИЕ может меняться. Не используйте его как ключ сверки.

planobject
допускает null

Текущий тариф этой eSIM.

current_codestring
—
expires_atstring · date-time
допускает null
—
started_atstring · date-time
допускает null
—
smdp_statusstring
допускает null
—
statusstring
—
updated_atstring · date-time
—
usageobject
допускает null

Статистика использованных данных eSIM. updated_at = null, если опроса ещё не было.

expires_atstring · date-time
допускает null
—
remaining_bytesinteger · int64
допускает null
—
sourcestring
допускает null
—
pollwebhook
total_bytesinteger · int64
допускает null
—
updated_atstring · date-time
допускает null
—
used_bytesinteger · int64
допускает null
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
money_settledboolean

Завершены ли расчёты по заказу, без возврата в обработке. При false счёт ещё нельзя выпустить.

netobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
public_refstring
допускает null

Короткий понятный код заказа для обращения в поддержку.

refundedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
statusstring
—
completedpending_providerprovider_failedcancelled
totalobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
okboolean
обязательно
—
true
201Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно

Заказ партнёра.

created_atstring · date-time
—
external_refstring
допускает null
—
fulfillment_statusstring

Фактически выданное количество — ОТДЕЛЬНО от status. Заказ из 3 eSIM с 2 выданными и 1 ошибкой остаётся status=completed, но имеет fulfillment_status=partially_fulfilled и refunded_amount > 0.

fulfilledpartially_fulfilledunfulfilled
idstring · uuid

ID заказа UUID.

idempotent_replayboolean
допускает null

true при идемпотентном повторе предыдущего успешного запроса.

invoice_nostring
допускает null

Номер счёта после выпуска.

invoice_statusstring

Статус счёта с НДС заказа. none — ещё не выпущен.

nonependingissuingissuedfailedcancelled
itemsobject[]
—
cancellableobject

Можно ли ещё отменить eSIM и почему нельзя. Прочитайте ДО POST /v1/esims/{order_item_id}/cancel. Блок присутствует ВСЕГДА и никогда не null.

allowedboolean
—
disclosed_atstring · date-time
допускает null

Время выдачи кода за пределы системы (ISO-8601 UTC), null, пока его никто не видел. Позволяет проверить жалобу «я не получил код, почему нельзя отменить».

reasonstring
допускает null

Причина запрета отмены: code_disclosed — код выдан или показан; wrong_status — неподходящее состояние; expired — срок истёк; not_supported — отмена не заявлена тарифом. null при allowed = true. Термины как в meta.reason ошибки esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
допускает null

Ваша ссылка заказа external_ref, переданная при покупке, для связи eSIM с вашей системой. null, если не указана.

iccidstring
допускает null
—
idstring · uuid

ID eSIM для всех путей /v1/esims/{id}/…, он же order_item_id вебхуков. Если известен только ICCID, получите id через GET /v1/esims?iccid=….

installobject
допускает null

Данные для УСТАНОВКИ eSIM. null означает, что вы их НЕ ЗАПРАШИВАЛИ (нет include=lpa), а не отсутствие кода. Если кода ещё нет, блок присутствует с lpa: null. Ответ с этим блоком всегда имеет Cache-Control: no-store.

android_urlstring
допускает null
—
apnstring
допускает null

APN для ручного ввода, если устройство не получает настройки автоматически. В текущей версии ВСЕГДА null, что означает «неизвестно», а не «не требуется».

ios_urlstring
допускает null
—
lpastring
допускает null

Код активации eSIM — секрет продажи. Этой строки достаточно для создания QR-кода у вас.

matching_idstring
допускает null
—
qr_code_base64string
допускает null

PNG data-URI QR-кода. Только при include=qr в ДЕТАЛЬНОМ методе. В списках его НЕТ (около 11 КБ на строку, то есть 1,1 МБ на 100 строк); создавайте QR из lpa.

smdp_addressstring
допускает null
—
msisdnstring
допускает null
—
order_idstring · uuid
—
package_codestring
допускает null

КОД тарифа из каталога для связи eSIM с вашим каталогом. null только если исходный тариф удалён из каталога.

package_namestring

Отображаемое НАЗВАНИЕ может меняться. Не используйте его как ключ сверки.

planobject
допускает null

Текущий тариф этой eSIM.

current_codestring
—
expires_atstring · date-time
допускает null
—
started_atstring · date-time
допускает null
—
smdp_statusstring
допускает null
—
statusstring
—
updated_atstring · date-time
—
usageobject
допускает null

Статистика использованных данных eSIM. updated_at = null, если опроса ещё не было.

expires_atstring · date-time
допускает null
—
remaining_bytesinteger · int64
допускает null
—
sourcestring
допускает null
—
pollwebhook
total_bytesinteger · int64
допускает null
—
updated_atstring · date-time
допускает null
—
used_bytesinteger · int64
допускает null
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
money_settledboolean

Завершены ли расчёты по заказу, без возврата в обработке. При false счёт ещё нельзя выпустить.

netobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
public_refstring
допускает null

Короткий понятный код заказа для обращения в поддержку.

refundedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
statusstring
—
completedpending_providerprovider_failedcancelled
totalobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failedinternal.invalid_filter
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
402
wallet.insufficient_balance
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
partner.package_not_visible
409
esim.out_of_stockidempotency.conflict
422
invoice.buyer_invalid
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="POST"
REQ_PATH="/v1/orders"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"external_ref":"po-1001","include":["lpa"],"invoice_buyer":{"address":"<address>","email":"<email>","full_name":"<full_name>","legal_name":"<legal_name>","tax_code":"TKE-VN-30D-X3K9F"},"items":[{"package_code":"TKE-VN-30D-X3K9F","quantity":2}]}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "created_at": "2026-07-11T08:12:30Z",
    "external_ref": "po-1001",
    "fulfillment_status": "fulfilled",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "idempotent_replay": true,
    "invoice_no": "<invoice_no>",
    "invoice_status": "none",
    "items": [
      {
        "cancellable": {
          "allowed": true,
          "disclosed_at": "2026-07-11T08:12:30Z",
          "reason": "code_disclosed"
        },
        "external_ref": "po-1001",
        "iccid": "8984012345678901234",
        "id": "018f2b00-0000-7000-8000-00000000009a",
        "install": {
          "android_url": "https://esimsetup.google.com/esim_qrcode_provisioning?data=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
          "apn": "<apn>",
          "ios_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
          "lpa": "LPA:1$smdp.example.com$MATCH-ABC123",
          "matching_id": "MATCH-ABC123",
          "qr_code_base64": "<PNG base64 — đã rút gọn>",
          "smdp_address": "smdp.example.com"
        },
        "msisdn": "+84900000000",
        "order_id": "018f2b00-0000-7000-8000-00000000009a",
        "package_code": "TKE-VN-30D-X3K9F",
        "package_name": "Tên gói hiển thị",
        "plan": {
          "current_code": "TKE-VN-30D-X3K9F",
          "expires_at": "2026-08-10T00:00:00Z",
          "started_at": "2026-07-11T08:12:30Z"
        },
        "smdp_status": "released",
        "status": "delivered",
        "updated_at": "2026-07-11T09:03:11Z",
        "usage": {
          "expires_at": "2026-08-10T00:00:00Z",
          "remaining_bytes": 5368709120,
          "source": "poll",
          "total_bytes": 5368709120,
          "updated_at": "2026-07-11T09:03:11Z",
          "used_bytes": 5368709120
        }
      }
    ],
    "mode": "live",
    "money_settled": true,
    "net": {
      "amount": 273000,
      "currency": "VND"
    },
    "public_ref": "TKE-DH-2A4C7Q",
    "refunded": {
      "amount": 273000,
      "currency": "VND"
    },
    "status": "completed",
    "total": {
      "amount": 273000,
      "currency": "VND"
    }
  },
  "ok": true
}

Пример тела запроса

json
{
  "external_ref": "po-1001",
  "include": [
    "lpa"
  ],
  "invoice_buyer": {
    "address": "<address>",
    "email": "<email>",
    "full_name": "<full_name>",
    "legal_name": "<legal_name>",
    "tax_code": "TKE-VN-30D-X3K9F"
  },
  "items": [
    {
      "package_code": "TKE-VN-30D-X3K9F",
      "quantity": 2
    }
  ]
}
GET/v1/orders/{id}

Детали заказа и eSIM

scope orders:readget_orders__id_

Требуемая область доступа: orders:read.

Параметры пути (1)

idstringобязательно

ID заказа UUID.

Параметры запроса (1)

includestringнеобязательно

Дополнительные блоки через запятую. lpa возвращает install с кодом активации; qr добавляет install.qr_code_base64 только в детальном ответе. ВОЗВРАТ КОДА СЧИТАЕТСЯ РАСКРЫТИЕМ. lpa НАВСЕГДА закрывает отмену всех eSIM ответа (cancellable.allowed = false, reason = code_disclosed): выданный код нельзя отозвать. Запрашивайте при передаче клиенту; при покупке заранее не запрашивайте, чтобы сохранить отмену. Неизвестное значение: internal.invalid_filter (400, field: "include").

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно

Заказ партнёра.

created_atstring · date-time
—
external_refstring
допускает null
—
fulfillment_statusstring

Фактически выданное количество — ОТДЕЛЬНО от status. Заказ из 3 eSIM с 2 выданными и 1 ошибкой остаётся status=completed, но имеет fulfillment_status=partially_fulfilled и refunded_amount > 0.

fulfilledpartially_fulfilledunfulfilled
idstring · uuid

ID заказа UUID.

idempotent_replayboolean
допускает null

true при идемпотентном повторе предыдущего успешного запроса.

invoice_nostring
допускает null

Номер счёта после выпуска.

invoice_statusstring

Статус счёта с НДС заказа. none — ещё не выпущен.

nonependingissuingissuedfailedcancelled
itemsobject[]
—
cancellableobject

Можно ли ещё отменить eSIM и почему нельзя. Прочитайте ДО POST /v1/esims/{order_item_id}/cancel. Блок присутствует ВСЕГДА и никогда не null.

allowedboolean
—
disclosed_atstring · date-time
допускает null

Время выдачи кода за пределы системы (ISO-8601 UTC), null, пока его никто не видел. Позволяет проверить жалобу «я не получил код, почему нельзя отменить».

reasonstring
допускает null

Причина запрета отмены: code_disclosed — код выдан или показан; wrong_status — неподходящее состояние; expired — срок истёк; not_supported — отмена не заявлена тарифом. null при allowed = true. Термины как в meta.reason ошибки esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
допускает null

Ваша ссылка заказа external_ref, переданная при покупке, для связи eSIM с вашей системой. null, если не указана.

iccidstring
допускает null
—
idstring · uuid

ID eSIM для всех путей /v1/esims/{id}/…, он же order_item_id вебхуков. Если известен только ICCID, получите id через GET /v1/esims?iccid=….

installobject
допускает null

Данные для УСТАНОВКИ eSIM. null означает, что вы их НЕ ЗАПРАШИВАЛИ (нет include=lpa), а не отсутствие кода. Если кода ещё нет, блок присутствует с lpa: null. Ответ с этим блоком всегда имеет Cache-Control: no-store.

android_urlstring
допускает null
—
apnstring
допускает null

APN для ручного ввода, если устройство не получает настройки автоматически. В текущей версии ВСЕГДА null, что означает «неизвестно», а не «не требуется».

ios_urlstring
допускает null
—
lpastring
допускает null

Код активации eSIM — секрет продажи. Этой строки достаточно для создания QR-кода у вас.

matching_idstring
допускает null
—
qr_code_base64string
допускает null

PNG data-URI QR-кода. Только при include=qr в ДЕТАЛЬНОМ методе. В списках его НЕТ (около 11 КБ на строку, то есть 1,1 МБ на 100 строк); создавайте QR из lpa.

smdp_addressstring
допускает null
—
msisdnstring
допускает null
—
order_idstring · uuid
—
package_codestring
допускает null

КОД тарифа из каталога для связи eSIM с вашим каталогом. null только если исходный тариф удалён из каталога.

package_namestring

Отображаемое НАЗВАНИЕ может меняться. Не используйте его как ключ сверки.

planobject
допускает null

Текущий тариф этой eSIM.

current_codestring
—
expires_atstring · date-time
допускает null
—
started_atstring · date-time
допускает null
—
smdp_statusstring
допускает null
—
statusstring
—
updated_atstring · date-time
—
usageobject
допускает null

Статистика использованных данных eSIM. updated_at = null, если опроса ещё не было.

expires_atstring · date-time
допускает null
—
remaining_bytesinteger · int64
допускает null
—
sourcestring
допускает null
—
pollwebhook
total_bytesinteger · int64
допускает null
—
updated_atstring · date-time
допускает null
—
used_bytesinteger · int64
допускает null
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
money_settledboolean

Завершены ли расчёты по заказу, без возврата в обработке. При false счёт ещё нельзя выпустить.

netobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
public_refstring
допускает null

Короткий понятный код заказа для обращения в поддержку.

refundedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
statusstring
—
completedpending_providerprovider_failedcancelled
totalobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failedinternal.invalid_filter
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
internal.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="GET"
REQ_PATH="/v1/orders/$ID"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "created_at": "2026-07-11T08:12:30Z",
    "external_ref": "po-1001",
    "fulfillment_status": "fulfilled",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "idempotent_replay": true,
    "invoice_no": "<invoice_no>",
    "invoice_status": "none",
    "items": [
      {
        "cancellable": {
          "allowed": true,
          "disclosed_at": "2026-07-11T08:12:30Z",
          "reason": "code_disclosed"
        },
        "external_ref": "po-1001",
        "iccid": "8984012345678901234",
        "id": "018f2b00-0000-7000-8000-00000000009a",
        "install": {
          "android_url": "https://esimsetup.google.com/esim_qrcode_provisioning?data=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
          "apn": "<apn>",
          "ios_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
          "lpa": "LPA:1$smdp.example.com$MATCH-ABC123",
          "matching_id": "MATCH-ABC123",
          "qr_code_base64": "<PNG base64 — đã rút gọn>",
          "smdp_address": "smdp.example.com"
        },
        "msisdn": "+84900000000",
        "order_id": "018f2b00-0000-7000-8000-00000000009a",
        "package_code": "TKE-VN-30D-X3K9F",
        "package_name": "Tên gói hiển thị",
        "plan": {
          "current_code": "TKE-VN-30D-X3K9F",
          "expires_at": "2026-08-10T00:00:00Z",
          "started_at": "2026-07-11T08:12:30Z"
        },
        "smdp_status": "released",
        "status": "delivered",
        "updated_at": "2026-07-11T09:03:11Z",
        "usage": {
          "expires_at": "2026-08-10T00:00:00Z",
          "remaining_bytes": 5368709120,
          "source": "poll",
          "total_bytes": 5368709120,
          "updated_at": "2026-07-11T09:03:11Z",
          "used_bytes": 5368709120
        }
      }
    ],
    "mode": "live",
    "money_settled": true,
    "net": {
      "amount": 273000,
      "currency": "VND"
    },
    "public_ref": "TKE-DH-2A4C7Q",
    "refunded": {
      "amount": 273000,
      "currency": "VND"
    },
    "status": "completed",
    "total": {
      "amount": 273000,
      "currency": "VND"
    }
  },
  "ok": true
}
POST/v1/orders/{id}/resend

Повторно отправить письмо/QR заказа

scope orders:writepost_orders__id__resend

Требуемая область доступа: orders:write.

Параметры пути (1)

idstringобязательно

ID заказа UUID.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
idstring · uuid

ID заказа, данные которого только что отправлены повторно.

modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
resent_atstring · date-time
—
resent_tostring

Адрес получателя, ЧАСТИЧНО СКРЫТЫЙ: партнёрский интерфейс не возвращает полный email клиента.

okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
409
order.not_deliverable
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="POST"
REQ_PATH="/v1/orders/$ID/resend"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "mode": "live",
    "resent_at": "2026-07-11T08:12:30Z",
    "resent_to": "ab***@x.com"
  },
  "ok": true
}
POST/v1/orders/bulk

Создать пакет из нескольких строк (заказ на строку)

scope orders:writeТребуется Idempotency-Keypost_orders_bulk

Требуемая область доступа: orders:write.

Создать пакет из нескольких строк: строка создаёт ОДИН независимый заказ тем же путём quote→commit, что POST /v1/orders. ЧАСТИЧНЫЙ УСПЕХ: ошибки не блокируют другие строки, списание только за успешные. Ошибки запаса, баланса, недоступности и лимитов — в lines[].error без смены HTTP-статуса. При нехватке баланса последующие более дешёвые строки всё равно проверяются. Статус всегда 200: ответ содержит результаты строк, не один ресурс и не отложенный процесс. Повтор после прерывания: ТЕ ЖЕ Idempotency-Key и тело загружают старый пакет, сохраняют завершённые результаты и выполняют только pending без двойного списания. Если строк не выполнялось, добавляется Idempotent-Replayed: true. Другое тело с тем же ключом: idempotency.conflict. Та же корзина за 10 минут в одной организации считается случайным дублем даже с разными ключами. Возвращается старый пакет с Idempotent-Replayed: true без списания. Для намеренной повторной покупки другому клиенту смените external_ref: он входит в подписанное тело, различает команды и помогает сверке. Полностью неуспешный пакет не блокируется; после 10 минут новый заказ разрешён. Ссылки строк возвращаются в lines[].external_ref, пакета — в корне. LPA/QR отсутствуют: получите eSIM через GET /v1/orders/{order_id} для каждого заказа.

Заголовки (1)

Idempotency-Keystringобязательно

Строка клиента: одинаковые ключ и тело выполняются ровно один раз; повтор возвращает исходный результат. Область действия — api_key_id (§11): разные ключи означают разные команды.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
external_refstring
допускает null

Ваша ссылка на ВЕСЬ пакет; возвращается в ответе пакета.

itemsobject[]
обязательно

Каждый элемент создаёт ОДИН независимый заказ. Лимит строк настраивается (по умолчанию 100); превышение даёт internal.limit_exceeded (422).

external_refstring
допускает null

Ваша ссылка ТОЛЬКО для этой строки; возвращается без изменений в результате строки.

package_codestring
обязательно

Код тарифа ВАШЕГО каталога, public_sku. НЕИЗМЕНЯЕМ для тарифа даже при смене поставщика, подходит для сверки. Пример имеет правильный ФОРМАТ, но не работает: получите реальный код из GET /v1/catalog/packages.

quantityinteger
обязательно
—

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно

Результат пакета заказов с ЧАСТИЧНЫМ УСПЕХОМ: ошибки строк не блокируют остальные; деньги списываются ТОЛЬКО для done. LPA/QR отсутствуют: получите eSIM через GET /v1/orders/{id} по каждому order_id.

created_atstring · date-time
—
external_refstring
допускает null
—
failed_linesinteger
—
idstring · uuid
—
itemsobject[]
—
chargedobject
допускает null
—
errorobject
допускает null

Код ошибки domain.reason, например wallet.insufficient_balance, esim.out_of_stock, order.package_not_allowed, wallet.velocity_exceeded.

codestring
—
messagestring
—
external_refstring
допускает null
—
line_nointeger

Нумерация с 1, в порядке отправки.

order_idstring · uuid
допускает null
—
package_codestring
—
quantityinteger
—
statusstring
—
pendingdonefailed
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
statusstring

pending — в пакете остались невыполненные строки после прерывания. Повторите с ТЕМ ЖЕ Idempotency-Key, чтобы завершить их.

pendingcompletedpartialfailed
succeeded_linesinteger
—
total_chargedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
total_linesinteger
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
partner.package_not_visible
409
idempotency.conflict
422
internal.limit_exceeded
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="POST"
REQ_PATH="/v1/orders/bulk"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"external_ref":"batch-2026-001","items":[{"external_ref":"po-1001","package_code":"TKE-VN-30D-X3K9F","quantity":5}]}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "created_at": "2026-07-11T08:12:30Z",
    "external_ref": "batch-2026-001",
    "failed_lines": 1,
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "items": [
      {
        "charged": null,
        "error": {
          "code": "TKE-VN-30D-X3K9F",
          "message": "<message>"
        },
        "external_ref": "po-1001",
        "line_no": 1,
        "order_id": "018f2b00-0000-7000-8000-00000000009a",
        "package_code": "TKE-VN-30D-X3K9F",
        "quantity": 2,
        "status": "pending"
      }
    ],
    "mode": "live",
    "status": "pending",
    "succeeded_lines": 1,
    "total_charged": {
      "amount": 273000,
      "currency": "VND"
    },
    "total_lines": 1
  },
  "ok": true
}

Пример тела запроса

json
{
  "external_ref": "batch-2026-001",
  "items": [
    {
      "external_ref": "po-1001",
      "package_code": "TKE-VN-30D-X3K9F",
      "quantity": 5
    }
  ]
}
GET/v1/orders/bulk/{id}

Результат пакета заказов

scope orders:readget_orders_bulk__id_

Требуемая область доступа: orders:read.

Повторное чтение результатов строк ранее созданного пакета при потере исходного ответа без повторной отправки тела. Пакет другой организации или режима (рабочий ⇄ тестовый) даёт 404.

Параметры пути (1)

idstringобязательно

ID пакета UUID, возвращённый POST /v1/orders/bulk.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно

Результат пакета заказов с ЧАСТИЧНЫМ УСПЕХОМ: ошибки строк не блокируют остальные; деньги списываются ТОЛЬКО для done. LPA/QR отсутствуют: получите eSIM через GET /v1/orders/{id} по каждому order_id.

created_atstring · date-time
—
external_refstring
допускает null
—
failed_linesinteger
—
idstring · uuid
—
itemsobject[]
—
chargedobject
допускает null
—
errorobject
допускает null

Код ошибки domain.reason, например wallet.insufficient_balance, esim.out_of_stock, order.package_not_allowed, wallet.velocity_exceeded.

codestring
—
messagestring
—
external_refstring
допускает null
—
line_nointeger

Нумерация с 1, в порядке отправки.

order_idstring · uuid
допускает null
—
package_codestring
—
quantityinteger
—
statusstring
—
pendingdonefailed
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
statusstring

pending — в пакете остались невыполненные строки после прерывания. Повторите с ТЕМ ЖЕ Idempotency-Key, чтобы завершить их.

pendingcompletedpartialfailed
succeeded_linesinteger
—
total_chargedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
total_linesinteger
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
internal.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="GET"
REQ_PATH="/v1/orders/bulk/$ID"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "created_at": "2026-07-11T08:12:30Z",
    "external_ref": "batch-2026-001",
    "failed_lines": 1,
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "items": [
      {
        "charged": null,
        "error": {
          "code": "TKE-VN-30D-X3K9F",
          "message": "<message>"
        },
        "external_ref": "po-1001",
        "line_no": 1,
        "order_id": "018f2b00-0000-7000-8000-00000000009a",
        "package_code": "TKE-VN-30D-X3K9F",
        "quantity": 2,
        "status": "pending"
      }
    ],
    "mode": "live",
    "status": "pending",
    "succeeded_lines": 1,
    "total_charged": {
      "amount": 273000,
      "currency": "VND"
    },
    "total_lines": 1
  },
  "ok": true
}

eSIM

eSIM: записи, расход, пополнение и отмена

GET/v1/esims

Список eSIM (сверка запросами)

scope esims:readget_esims

Требуемая область доступа: esims:read.

Параметры запроса (8)

page_sizeintegerнеобязательнопо умолчанию 50min 1max 200

Записей на странице. По умолчанию 50, максимум 200. Большее значение ограничивается 200, это НЕ ошибка.

statusstringнеобязательно
Допустимые значения:pendingavailablereservedsolddeliveredin_useused_upexpiredsuspendedcancelledprovider_failed

Фильтр по esim_status. Значение вне списка даёт internal.invalid_filter (400), а не пустой список: опечатка вроде canceled без второго l не должна выглядеть как отсутствие eSIM в этом состоянии.

order_idstring · uuidнеобязательно

Только eSIM одного заказа.

iccidstringнеобязательно

Фильтр по ICCID (строка, точное совпадение).

external_refstringнеобязательно

Все eSIM заказов с вашей ссылкой заказа (external_ref, переданной при покупке, точное совпадение).

updated_sincestring · date-timeнеобязательно

ISO-8601 UTC. Записи с updated_at >= updated_since по возрастанию — стандартная СВЕРКА ЗАПРОСАМИ (§8.5), позволяющая восстановить пропущенный вебхук по состоянию. Неверный формат: internal.invalid_filter (400).

cursorstringнеобязательно

Курсор следующей страницы: передайте next_cursor предыдущей страницы БЕЗ ИЗМЕНЕНИЙ. ВНИМАНИЕ: неверный курсор не вызывает ошибку, сервер снова читает НАЧАЛО списка для совместимости при смене кодирования. Поэтому перед повтором проверяйте, что next_cursor отличается от отправленного курсора, иначе цикл будет бесконечно читать первую страницу.

includestringнеобязательно

Дополнительные блоки через запятую. lpa возвращает install с кодом активации; qr добавляет install.qr_code_base64 только в детальном ответе. ВОЗВРАТ КОДА СЧИТАЕТСЯ РАСКРЫТИЕМ. lpa НАВСЕГДА закрывает отмену всех eSIM ответа (cancellable.allowed = false, reason = code_disclosed): выданный код нельзя отозвать. Запрашивайте при передаче клиенту; при покупке заранее не запрашивайте, чтобы сохранить отмену. Неизвестное значение: internal.invalid_filter (400, field: "include").

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
itemsobject[]
—
cancellableobject

Можно ли ещё отменить eSIM и почему нельзя. Прочитайте ДО POST /v1/esims/{order_item_id}/cancel. Блок присутствует ВСЕГДА и никогда не null.

allowedboolean
—
disclosed_atstring · date-time
допускает null

Время выдачи кода за пределы системы (ISO-8601 UTC), null, пока его никто не видел. Позволяет проверить жалобу «я не получил код, почему нельзя отменить».

reasonstring
допускает null

Причина запрета отмены: code_disclosed — код выдан или показан; wrong_status — неподходящее состояние; expired — срок истёк; not_supported — отмена не заявлена тарифом. null при allowed = true. Термины как в meta.reason ошибки esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
допускает null

Ваша ссылка заказа external_ref, переданная при покупке, для связи eSIM с вашей системой. null, если не указана.

iccidstring
допускает null
—
idstring · uuid

ID eSIM для всех путей /v1/esims/{id}/…, он же order_item_id вебхуков. Если известен только ICCID, получите id через GET /v1/esims?iccid=….

installobject
допускает null

Данные для УСТАНОВКИ eSIM. null означает, что вы их НЕ ЗАПРАШИВАЛИ (нет include=lpa), а не отсутствие кода. Если кода ещё нет, блок присутствует с lpa: null. Ответ с этим блоком всегда имеет Cache-Control: no-store.

android_urlstring
допускает null
—
apnstring
допускает null

APN для ручного ввода, если устройство не получает настройки автоматически. В текущей версии ВСЕГДА null, что означает «неизвестно», а не «не требуется».

ios_urlstring
допускает null
—
lpastring
допускает null

Код активации eSIM — секрет продажи. Этой строки достаточно для создания QR-кода у вас.

matching_idstring
допускает null
—
qr_code_base64string
допускает null

PNG data-URI QR-кода. Только при include=qr в ДЕТАЛЬНОМ методе. В списках его НЕТ (около 11 КБ на строку, то есть 1,1 МБ на 100 строк); создавайте QR из lpa.

smdp_addressstring
допускает null
—
msisdnstring
допускает null
—
order_idstring · uuid
—
package_codestring
допускает null

КОД тарифа из каталога для связи eSIM с вашим каталогом. null только если исходный тариф удалён из каталога.

package_namestring

Отображаемое НАЗВАНИЕ может меняться. Не используйте его как ключ сверки.

planobject
допускает null

Текущий тариф этой eSIM.

current_codestring
—
expires_atstring · date-time
допускает null
—
started_atstring · date-time
допускает null
—
smdp_statusstring
допускает null
—
statusstring
—
updated_atstring · date-time
—
usageobject
допускает null

Статистика использованных данных eSIM. updated_at = null, если опроса ещё не было.

expires_atstring · date-time
допускает null
—
remaining_bytesinteger · int64
допускает null
—
sourcestring
допускает null
—
pollwebhook
total_bytesinteger · int64
допускает null
—
updated_atstring · date-time
допускает null
—
used_bytesinteger · int64
допускает null
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_cursorstring
допускает null

Передайте БЕЗ ИЗМЕНЕНИЙ для следующей страницы. null — БОЛЬШЕ ДАННЫХ НЕТ, единственный сигнал остановки: за страницей короче page_size могут оставаться другие.

page_sizeinteger
допускает null

МАКСИМАЛЬНОЕ число записей, использованное сервером для страницы с учётом ограничения, а не фактическая длина items.

totalinteger · int64
допускает null

Число строк по фильтру. null, если подсчёт слишком дорог. Конец списка определяйте по next_cursor, а не по этому полю.

warningsobject[]

ПРИЧИНЫ состава списка. Пусто — особенностей нет. code — ЗАКРЫТЫЙ набор: filtered_by_key_package_filters (ограничения каталога ключа), filtered_by_provider_scope (поставщики организации), no_price_for_org (нет цены), price_filter_removed_rows (фильтр цены), provider_disabled (нет доступного поставщика), capability_missing (не заявлены действия после продажи). count — число затронутых строк или null, если не выразимо числом.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
допускает null
—
messagestring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failedinternal.invalid_filter
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="GET"
REQ_PATH="/v1/esims"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "items": [
      {
        "cancellable": {
          "allowed": true,
          "disclosed_at": "2026-07-11T08:12:30Z",
          "reason": "code_disclosed"
        },
        "external_ref": "po-1001",
        "iccid": "8984012345678901234",
        "id": "018f2b00-0000-7000-8000-00000000009a",
        "install": {
          "android_url": "https://esimsetup.google.com/esim_qrcode_provisioning?data=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
          "apn": "<apn>",
          "ios_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
          "lpa": "LPA:1$smdp.example.com$MATCH-ABC123",
          "matching_id": "MATCH-ABC123",
          "qr_code_base64": "<PNG base64 — đã rút gọn>",
          "smdp_address": "smdp.example.com"
        },
        "msisdn": "+84900000000",
        "order_id": "018f2b00-0000-7000-8000-00000000009a",
        "package_code": "TKE-VN-30D-X3K9F",
        "package_name": "Tên gói hiển thị",
        "plan": {
          "current_code": "TKE-VN-30D-X3K9F",
          "expires_at": "2026-08-10T00:00:00Z",
          "started_at": "2026-07-11T08:12:30Z"
        },
        "smdp_status": "released",
        "status": "delivered",
        "updated_at": "2026-07-11T09:03:11Z",
        "usage": {
          "expires_at": "2026-08-10T00:00:00Z",
          "remaining_bytes": 5368709120,
          "source": "poll",
          "total_bytes": 5368709120,
          "updated_at": "2026-07-11T09:03:11Z",
          "used_bytes": 5368709120
        }
      }
    ],
    "mode": "live",
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNy0xMVQwODoxMjozMFoifQ",
    "page_size": 50,
    "total": 128,
    "warnings": []
  },
  "ok": true
}
GET/v1/esims/{id}

Получить одну eSIM

scope esims:readget_esims__id_

Требуемая область доступа: esims:read.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Параметры запроса (1)

includestringнеобязательно

Дополнительные блоки через запятую. lpa возвращает install с кодом активации; qr добавляет install.qr_code_base64 только в детальном ответе. ВОЗВРАТ КОДА СЧИТАЕТСЯ РАСКРЫТИЕМ. lpa НАВСЕГДА закрывает отмену всех eSIM ответа (cancellable.allowed = false, reason = code_disclosed): выданный код нельзя отозвать. Запрашивайте при передаче клиенту; при покупке заранее не запрашивайте, чтобы сохранить отмену. Неизвестное значение: internal.invalid_filter (400, field: "include").

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
cancellableobject

Можно ли ещё отменить eSIM и почему нельзя. Прочитайте ДО POST /v1/esims/{order_item_id}/cancel. Блок присутствует ВСЕГДА и никогда не null.

allowedboolean
—
disclosed_atstring · date-time
допускает null

Время выдачи кода за пределы системы (ISO-8601 UTC), null, пока его никто не видел. Позволяет проверить жалобу «я не получил код, почему нельзя отменить».

reasonstring
допускает null

Причина запрета отмены: code_disclosed — код выдан или показан; wrong_status — неподходящее состояние; expired — срок истёк; not_supported — отмена не заявлена тарифом. null при allowed = true. Термины как в meta.reason ошибки esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
допускает null

Ваша ссылка заказа external_ref, переданная при покупке, для связи eSIM с вашей системой. null, если не указана.

iccidstring
допускает null
—
idstring · uuid

ID eSIM для всех путей /v1/esims/{id}/…, он же order_item_id вебхуков. Если известен только ICCID, получите id через GET /v1/esims?iccid=….

installobject
допускает null

Данные для УСТАНОВКИ eSIM. null означает, что вы их НЕ ЗАПРАШИВАЛИ (нет include=lpa), а не отсутствие кода. Если кода ещё нет, блок присутствует с lpa: null. Ответ с этим блоком всегда имеет Cache-Control: no-store.

android_urlstring
допускает null
—
apnstring
допускает null

APN для ручного ввода, если устройство не получает настройки автоматически. В текущей версии ВСЕГДА null, что означает «неизвестно», а не «не требуется».

ios_urlstring
допускает null
—
lpastring
допускает null

Код активации eSIM — секрет продажи. Этой строки достаточно для создания QR-кода у вас.

matching_idstring
допускает null
—
qr_code_base64string
допускает null

PNG data-URI QR-кода. Только при include=qr в ДЕТАЛЬНОМ методе. В списках его НЕТ (около 11 КБ на строку, то есть 1,1 МБ на 100 строк); создавайте QR из lpa.

smdp_addressstring
допускает null
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
msisdnstring
допускает null
—
order_idstring · uuid
—
package_codestring
допускает null

КОД тарифа из каталога для связи eSIM с вашим каталогом. null только если исходный тариф удалён из каталога.

package_namestring

Отображаемое НАЗВАНИЕ может меняться. Не используйте его как ключ сверки.

planobject
допускает null

Текущий тариф этой eSIM.

current_codestring
—
expires_atstring · date-time
допускает null
—
started_atstring · date-time
допускает null
—
smdp_statusstring
допускает null
—
statusstring
—
updated_atstring · date-time
—
usageobject
допускает null

Статистика использованных данных eSIM. updated_at = null, если опроса ещё не было.

expires_atstring · date-time
допускает null
—
remaining_bytesinteger · int64
допускает null
—
sourcestring
допускает null
—
pollwebhook
total_bytesinteger · int64
допускает null
—
updated_atstring · date-time
допускает null
—
used_bytesinteger · int64
допускает null
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failedinternal.invalid_filter
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
internal.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="GET"
REQ_PATH="/v1/esims/$ID"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "cancellable": {
      "allowed": true,
      "disclosed_at": "2026-07-11T08:12:30Z",
      "reason": "code_disclosed"
    },
    "external_ref": "po-1001",
    "iccid": "8984012345678901234",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "install": {
      "android_url": "https://esimsetup.google.com/esim_qrcode_provisioning?data=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
      "apn": "<apn>",
      "ios_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
      "lpa": "LPA:1$smdp.example.com$MATCH-ABC123",
      "matching_id": "MATCH-ABC123",
      "qr_code_base64": "<PNG base64 — đã rút gọn>",
      "smdp_address": "smdp.example.com"
    },
    "mode": "live",
    "msisdn": "+84900000000",
    "order_id": "018f2b00-0000-7000-8000-00000000009a",
    "package_code": "TKE-VN-30D-X3K9F",
    "package_name": "Tên gói hiển thị",
    "plan": {
      "current_code": "TKE-VN-30D-X3K9F",
      "expires_at": "2026-08-10T00:00:00Z",
      "started_at": "2026-07-11T08:12:30Z"
    },
    "smdp_status": "released",
    "status": "delivered",
    "updated_at": "2026-07-11T09:03:11Z",
    "usage": {
      "expires_at": "2026-08-10T00:00:00Z",
      "remaining_bytes": 5368709120,
      "source": "poll",
      "total_bytes": 5368709120,
      "updated_at": "2026-07-11T09:03:11Z",
      "used_bytes": 5368709120
    }
  },
  "ok": true
}
POST/v1/esims/{id}/cancel

Отменить неиспользованную eSIM

scope esims:writeТребуется Idempotency-Keypost_esims__id__cancel

Требуемая область доступа: esims:write.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Заголовки (1)

Idempotency-Keystringобязательно

Строка клиента: одинаковые ключ и тело выполняются ровно один раз; повтор возвращает исходный результат. Область действия — api_key_id (§11): разные ключи означают разные команды.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
cancelled_atstring · date-time
—
esim_idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
refund_suggestedboolean

true — запрос возврата ожидает одобрения человеком, деньги ЕЩЁ НЕ переведены. refunded.amount = 0 не означает отказ от возврата.

refundedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
statusstring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
409
esim.not_cancellableidempotency.conflict
422
capability.not_supported
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="POST"
REQ_PATH="/v1/esims/$ID/cancel"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Idempotency-Key: $(uuidgen)"

Пример ответа

json
{
  "data": {
    "cancelled_at": "2026-07-11T08:12:30Z",
    "esim_id": "018f2b00-0000-7000-8000-000000000000",
    "mode": "live",
    "refund_suggested": true,
    "refunded": {
      "amount": 273000,
      "currency": "VND"
    },
    "status": "cancelled"
  },
  "ok": true
}
POST/v1/esims/{id}/change-plan

Пополнить eSIM

scope esims:writeТребуется Idempotency-Keypost_esims__id__change-plan

Требуемая область доступа: esims:write.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Заголовки (1)

Idempotency-Keystringобязательно

Строка клиента: одинаковые ключ и тело выполняются ровно один раз; повтор возвращает исходный результат. Область действия — api_key_id (§11): разные ключи означают разные команды.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
package_codestring
обязательно

Код тарифа ВАШЕГО каталога, public_sku. НЕИЗМЕНЯЕМ для тарифа даже при смене поставщика, подходит для сверки. Пример имеет правильный ФОРМАТ, но не работает: получите реальный код из GET /v1/catalog/packages.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно

Результат смены тарифа eSIM.

chargedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
current_planobject
—
codestring
—
expires_atstring · date-time
допускает null
—
started_atstring · date-time
допускает null
—
esim_idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
order_idstring · uuid
—
previous_planobject
допускает null
—
cancelled_atstring · date-time
допускает null
—
codestring
—
remaining_bytes_forfeitedinteger · int64
допускает null
—
warningstring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
402
wallet.insufficient_balance
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
partner.package_not_visible
409
esim.topup_not_supportedesim.topup_provider_mismatchesim.not_eligibleidempotency.conflict
422
capability.not_supported
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="POST"
REQ_PATH="/v1/esims/$ID/change-plan"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"package_code":"TKE-VN-7D-P4M2Q"}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "charged": {
      "amount": 273000,
      "currency": "VND"
    },
    "current_plan": {
      "code": "TKE-VN-30D-X3K9F",
      "expires_at": "2026-08-10T00:00:00Z",
      "started_at": "2026-07-11T08:12:30Z"
    },
    "esim_id": "018f2b00-0000-7000-8000-000000000000",
    "mode": "live",
    "order_id": "018f2b00-0000-7000-8000-00000000009a",
    "previous_plan": {
      "cancelled_at": "2026-07-11T08:12:30Z",
      "code": "TKE-VN-30D-X3K9F",
      "remaining_bytes_forfeited": 2147483648
    },
    "warning": "Dung lượng còn lại của gói cũ KHÔNG được cộng dồn."
  },
  "ok": true
}

Пример тела запроса

json
{
  "package_code": "TKE-VN-7D-P4M2Q"
}
GET/v1/esims/{id}/plan-options

Доступные тарифы пополнения этой eSIM

scope esims:readget_esims__id__plan-options

Требуемая область доступа: esims:read.

Доступные тарифы пополнения ИМЕННО ЭТОЙ eSIM. Сервер находит её поставщика по id и возвращает только его тарифы. /v1 не раскрывает provider_id, поэтому фильтрация исключает выбор другого поставщика. Прямой POST /v1/esims/{id}/change-plan с тарифом вне списка даёт 409 esim.topup_provider_mismatch. Без связанного поставщика (импортированная запись или отсутствующие исходные данные) возвращается ПУСТОЙ список total: 0; это корректный ответ.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Параметры запроса (2)

page_sizeintegerнеобязательнопо умолчанию 50min 1max 200

Записей на странице. По умолчанию 50, максимум 200. Большее значение ограничивается 200, это НЕ ошибка.

cursorstringнеобязательно

Курсор следующей страницы: передайте next_cursor предыдущей страницы БЕЗ ИЗМЕНЕНИЙ. ВНИМАНИЕ: неверный курсор не вызывает ошибку, сервер снова читает НАЧАЛО списка для совместимости при смене кодирования. Поэтому перед повтором проверяйте, что next_cursor отличается от отправленного курсора, иначе цикл будет бесконечно читать первую страницу.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно

Тарифы пополнения ЭТОЙ eSIM, уже отфильтрованные по её поставщику. Каждый элемент — полный Package, как в каталоге, без второй схемы. policy относится к поставщику, СЕЙЧАС обслуживающему eSIM. null — политика не проверена: не делайте предположений, это НЕ обещание отсутствия отмены.

itemsobject[]
—
capabilitiesobject

Доступные действия ПОСЛЕ продажи из РЕАЛЬНОЙ декларации тарифа, без предположений по типу поставщика. Без декларации все флаги false, чтобы не обещать метод с неизбежным 422 capability.not_supported.

cancelobject
—
restrictionstring
допускает null

code_not_disclosed — отмена возможна, пока НИКТО не видел код активации: не отправлен клиенту и не прочитан через панель или ?include=lpa. Это РЕАЛЬНОЕ условие отмены. Оно ничего не говорит об установке клиентом, которую система не может знать; прежнее unused_only ошибочно обещало обратное.

code_not_disclosednull
supportedboolean
—
plan_changeobject
—
policyobject
допускает null

Политика ДОБАВЛЕНИЯ НОВОГО ТАРИФА поставщика. null — тариф нельзя пополнить ИЛИ политика ещё не проверена. Это означает «не делайте предположений», а не объект с false, который обещал бы отсутствие отмены.

……

Вложенные поля смотрите в примере JSON.

supportedboolean
—
usage_checkobject
—
supportedboolean
—
codestring

Код тарифа ВАШЕГО каталога, public_sku. НЕИЗМЕНЯЕМ для тарифа даже при смене поставщика, подходит для сверки. Пример имеет правильный ФОРМАТ, но не работает: получите реальный код из GET /v1/catalog/packages.

coverageobject

Где работает тариф.

countriesstring[]

Коды ISO-3166 alpha-2. Пустой массив у глобальных тарифов (200 стран не перечисляются) и тарифов без заявленного покрытия.

country_countinteger

Количество стран покрытия для сортировки или фильтрации многострановых тарифов без загрузки и подсчёта всего массива.

groupstring

Тип покрытия: страны country, регион region или весь мир global. ОТКРЫТОЕ перечисление: игнорируйте неизвестные значения.

regionstring
допускает null

Пока ВСЕГДА null: регионы связаны со СТРАНАМИ, а не тарифами. Значение было бы предположением. Фильтр ?region= продолжает работать.

dataobject

Лимит данных тарифа. ВСЕ значения в БАЙТАХ, не МБ/ГБ, чтобы избежать двойного округления.

daily_bytesinteger · int64
допускает null

ДНЕВНОЙ лимит данных в байтах. null для fixed/unlimited.

labelstring

Понятная пользователю подпись, созданная общей для всех экранов функцией.

modelstring

Учёт лимита: daily (на день) | fixed (на весь срок) | unlimited. ОТКРЫТОЕ перечисление: клиент ОБЯЗАН игнорировать неизвестные значения.

dailyfixedunlimited
resetobject
допускает null

Момент сброса дневного лимита. Неизвестное время может лишить купившего в 22:00 почти всего первого дня. null — не заявлено.

atstring
допускает null

Местное время сброса в часовом поясе timezone (00:00, 00:00:01).

notestring
допускает null
—
timezonestring
допускает null

Часовой пояс сброса (UTC+8, UTC+7).

total_bytesinteger · int64
допускает null

Общий лимит на весь срок в байтах. null для daily/unlimited: произведение дневного лимита на дни не является обязательством поставщика.

descriptionstring
допускает null

Описание тарифа для продажи. null, если ещё не написано.

description_enstring
допускает null

Описание на английском. null, если ещё не задано.

featuresobject

Возможности тарифа. ТИПИЗИРОВАННЫЙ объект: массив [] не различает отсутствие раздачи и отсутствие сведений, а false и null различают.

appsobject[]

Поддержка приложения по ОПЕРАЦИОННЫМ СИСТЕМАМ. Отсутствие приложения в массиве означает ОТСУТСТВИЕ ДАННЫХ, а не поддержку. Поставщик гарантирует только перечисленное; пока заявлен только TikTok. Объединённого флага нет намеренно: в реальном каталоге 2465 тарифов поддерживают TikTok на iPhone, но не Android. Единый флаг исказил бы почти треть каталога.

androidstring

supported — поставщик заявляет поддержку; blocked — запрет; unknown — нет декларации для этой ОС. ОТКРЫТОЕ перечисление.

supportedblockedunknown
appstring

Код приложения в нижнем регистре, например tiktok.

iosstring

supported — поставщик заявляет поддержку; blocked — запрет; unknown — нет декларации для этой ОС. ОТКРЫТОЕ перечисление.

supportedblockedunknown
notestring
допускает null
—
hotspotboolean
допускает null

Раздача Wi-Fi другим устройствам. null — не заявлено и ОТЛИЧАЕТСЯ от false.

kyc_requiredboolean
допускает null

Перед использованием требуется проверка личности KYC. null — не заявлено.

smsboolean
допускает null

SMS. false — сообщения НЕ принимаются, в том числе банковские OTP. null — не заявлено.

voiceboolean
допускает null

Голосовые вызовы. false — поставщик заявляет ОТСУТСТВИЕ голоса: линия только данных не принимает вызовы. null — не заявлено.

fupobject
допускает null

Поведение после исчерпания скоростного лимита. null — сведения об ограничении не заявлены.

behaviorstring
допускает null

throttle — замедление; cutoff — отключение; null — не заявлено. ОТКРЫТОЕ перечисление.

throttlecutoffnull
high_speed_bytesinteger · int64
допускает null

Объём в БАЙТАХ на полной скорости до ограничения.

notestring
допускает null
—
speed_kbpsinteger
допускает null

Скорость ПОСЛЕ исчерпания скоростного лимита, кбит/с. null при отключении или отсутствии декларации. ОТЛИЧАЕТСЯ от network.speed_cap_kbps, ограничивающего весь тариф.

marketstring

СИСТЕМА ПОСТАВКИ, ОТКРЫТОЕ перечисление: игнорируйте неизвестные значения. Определяет запас и действия после продажи: domestic продаёт комплекты с пополнением, отменой и проверкой расхода; international заказывается у поставщика по запросу без запаса.

domesticinternational
namestring
—
name_enstring
допускает null

Название на английском. null, если ещё не задано.

networkobject

Сети, в которых работает тариф.

apnstring
допускает null

APN для ручного ввода на Android, если устройство не подхватило его автоматически. Требуется для инструкции установки, иначе клиенту понадобится поддержка. null — не заявлено.

carriersobject[]

Операторы связи, В СЕТЯХ которых работает тариф. Характеристика eSIM, а не поставщик.

namestring
—
generationsstring[]
допускает null

Доступные поколения сети (["4g", "5g"]). null — не заявлено, а НЕ «только 2G».

speed_cap_kbpsinteger
допускает null

Ограничение скорости ВСЕГО тарифа, кбит/с, в отличие от fup.speed_kbps после исчерпания скоростного лимита.

priceobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
tagsstring[]

Нормализованные коммерческие метки: нижний регистр, дефисы. По ним также работают ограничения каталога ключа. Пока меток у тарифов нет, ?tag= возвращает пустой список до начала их назначения продавцом.

validityobject

Срок действия тарифа и момент начала отсчёта.

daysinteger
—
install_by_daysinteger
допускает null

Число дней после покупки для сканирования QR и установки eSIM. null — не заявлено, а НЕ «устанавливайте когда угодно».

labelstring
—
reclaim_on_expiryboolean
допускает null

Поставщик ИЗЫМАЕТ подписку после истечения тарифа: пополнение и смена становятся невозможны. Важный срок при продаже продлений. null — не заявлено.

starts_whenstring
допускает null

КОГДА начинается срок: on_install (установка eSIM клиентом) | on_purchase (покупка) | null. Критически важное поле: незнание отсчёта от УСТАНОВКИ приведёт к неверному обещанию срока каждому клиенту. При null не обещайте точную дату.

on_installon_purchasenull
unused_valid_daysinteger
допускает null

Число дней хранения неиспользованного тарифа до истечения срока. null — не заявлено, НЕ бессрочно. Отдельно от install_by_days: одно — срок УСТАНОВКИ, другое — ХРАНЕНИЯ.

modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_cursorstring
допускает null

Передайте БЕЗ ИЗМЕНЕНИЙ для следующей страницы. null — БОЛЬШЕ ДАННЫХ НЕТ, единственный сигнал остановки: за страницей короче page_size могут оставаться другие.

page_sizeinteger

МАКСИМАЛЬНОЕ число записей, использованное сервером для страницы с учётом ограничения, а не фактическая длина items.

policyobject
допускает null

Политика ДОБАВЛЕНИЯ НОВОГО ТАРИФА поставщика. null — тариф нельзя пополнить ИЛИ политика ещё не проверена. Это означает «не делайте предположений», а не объект с false, который обещал бы отсутствие отмены.

notestring
допускает null
—
requires_cancel_firstboolean

ТЕКУЩИЙ тариф отменяется до добавления нового: этот поставщик не складывает тарифы. Сообщите клиенту ДО пополнения новым тарифом.

stackingboolean

Добавляются ли срок и лимит нового тарифа к текущему.

totalinteger · int64
допускает null

Общее число тарифов по фильтрам. null при дорогом подсчёте (международная система или без market). Подсчёт идёт ДО ограничений каталога ключа и наличия цены организации: это число совпадений в каталоге, а не доступных вам покупок.

warningsobject[]

ПРИЧИНЫ состава списка. Пусто — особенностей нет. code — ЗАКРЫТЫЙ набор: filtered_by_key_package_filters (ограничения каталога ключа), filtered_by_provider_scope (поставщики организации), no_price_for_org (нет цены), price_filter_removed_rows (фильтр цены), provider_disabled (нет доступного поставщика), capability_missing (не заявлены действия после продажи). count — число затронутых строк или null, если не выразимо числом.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
допускает null
—
messagestring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
internal.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="GET"
REQ_PATH="/v1/esims/$ID/plan-options"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "items": [
      {
        "capabilities": {
          "cancel": {
            "restriction": "code_not_disclosed",
            "supported": true
          },
          "plan_change": {
            "policy": {
              "note": "<note>",
              "requires_cancel_first": true,
              "stacking": true
            },
            "supported": true
          },
          "usage_check": {
            "supported": true
          }
        },
        "code": "TKE-VN-30D-X3K9F",
        "coverage": {
          "countries": [
            "CN"
          ],
          "country_count": 1,
          "group": "country",
          "region": "<region>"
        },
        "data": {
          "daily_bytes": 1073741824,
          "label": "1GB/ngày",
          "model": "daily",
          "reset": {
            "at": "00:00",
            "note": "<note>",
            "timezone": "UTC+8"
          },
          "total_bytes": null
        },
        "description": "<description>",
        "description_en": "<description_en>",
        "features": {
          "apps": [
            {
              "android": "blocked",
              "app": "tiktok",
              "ios": "supported",
              "note": "<note>"
            }
          ],
          "hotspot": true,
          "kyc_required": false,
          "sms": false,
          "voice": false
        },
        "fup": {
          "behavior": "throttle",
          "high_speed_bytes": 1048576,
          "note": "<note>",
          "speed_kbps": 128
        },
        "market": "international",
        "name": "eSIM Trung Quốc Mainland Multi - 1GB/ngày - 24 ngày",
        "name_en": "<name_en>",
        "network": {
          "apn": "e-ideas",
          "carriers": [
            {
              "name": "China Unicom"
            }
          ],
          "generations": [
            "4g",
            "5g"
          ],
          "speed_cap_kbps": 1
        },
        "price": {
          "amount": 273000,
          "currency": "VND"
        },
        "tags": [],
        "validity": {
          "days": 24,
          "install_by_days": 365,
          "label": "24 ngày",
          "reclaim_on_expiry": true,
          "starts_when": "on_install",
          "unused_valid_days": 1
        }
      }
    ],
    "mode": "live",
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNy0xMVQwODoxMjozMFoifQ",
    "page_size": 50,
    "policy": {
      "note": "<note>",
      "requires_cancel_first": true,
      "stacking": true
    },
    "total": 128,
    "warnings": []
  },
  "ok": true
}
GET/v1/esims/{id}/refund-requests

История заявок на возврат eSIM (только участники)

scope esims:readget_esims__id__refund-requests

Требуемая область доступа: esims:read.

Только разрешённые рабочие ключи участников. Возвращает до 50 последних заявок по eSIM участника, начиная с новых; next_cursor и total равны null. has_open_request указывает на ожидание проверки. Ключи организаций и тестовые ключи получают capability.not_supported.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
has_open_requestboolean
—
itemsobject[]
—
decided_atstring · date-time
допускает null
—
decision_notestring
допускает null
—
idstring · uuid
—
order_idstring · uuid
—
order_item_idstring · uuid
—
reasonstring
—
refundedobject
допускает null
—
requested_atstring · date-time
—
statusstring
—
pendingapprovedrejected
suggestedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_cursorstring
допускает null

Передайте БЕЗ ИЗМЕНЕНИЙ для следующей страницы. null — БОЛЬШЕ ДАННЫХ НЕТ, единственный сигнал остановки: за страницей короче page_size могут оставаться другие.

page_sizeinteger
допускает null

МАКСИМАЛЬНОЕ число записей, использованное сервером для страницы с учётом ограничения, а не фактическая длина items.

totalinteger · int64
допускает null

Число строк по фильтру. null, если подсчёт слишком дорог. Конец списка определяйте по next_cursor, а не по этому полю.

warningsobject[]

ПРИЧИНЫ состава списка. Пусто — особенностей нет. code — ЗАКРЫТЫЙ набор: filtered_by_key_package_filters (ограничения каталога ключа), filtered_by_provider_scope (поставщики организации), no_price_for_org (нет цены), price_filter_removed_rows (фильтр цены), provider_disabled (нет доступного поставщика), capability_missing (не заявлены действия после продажи). count — число затронутых строк или null, если не выразимо числом.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
допускает null
—
messagestring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
internal.not_found
422
capability.not_supported
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_member_live_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="GET"
REQ_PATH="/v1/esims/$ID/refund-requests"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "has_open_request": true,
    "items": [
      {
        "decided_at": "2026-07-11T08:12:30Z",
        "decision_note": "<decision_note>",
        "id": "018f2b00-0000-7000-8000-00000000009a",
        "order_id": "018f2b00-0000-7000-8000-00000000009a",
        "order_item_id": "018f2b00-0000-7000-8000-000000000000",
        "reason": "<reason>",
        "refunded": null,
        "requested_at": "2026-07-11T08:12:30Z",
        "status": "pending",
        "suggested": {
          "amount": 273000,
          "currency": "VND"
        }
      }
    ],
    "mode": "live",
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNy0xMVQwODoxMjozMFoifQ",
    "page_size": 50,
    "total": 128,
    "warnings": []
  },
  "ok": true
}
POST/v1/esims/{id}/refund-requests

Создать заявку на возврат eSIM (только участники)

scope esims:writeТребуется Idempotency-Keypost_esims__id__refund-requests

Требуемая область доступа: esims:write.

Только разрешённые рабочие ключи участников. Заявка на возврат за собственную eSIM требует Idempotency-Key; средства не переводятся до одобрения администратора. Ключи организаций и тестовые ключи получают capability.not_supported. Для проверки виртуального возврата в тестовом режиме отмените eSIM.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Заголовки (1)

Idempotency-Keystringобязательно

Строка клиента: одинаковые ключ и тело выполняются ровно один раз; повтор возвращает исходный результат. Область действия — api_key_id (§11): разные ключи означают разные команды.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
reasonstring
обязательно

Причина возврата, от 5 до 1000 символов после удаления пробелов по краям.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
decided_atstring · date-time
допускает null
—
decision_notestring
допускает null
—
idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
order_idstring · uuid
—
order_item_idstring · uuid
—
reasonstring
—
refundedobject
допускает null
—
requested_atstring · date-time
—
statusstring
—
pendingapprovedrejected
suggestedobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
internal.not_found
409
esim.refund_not_eligibleesim.refund_request_pendingidempotency.conflict
422
capability.not_supported
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_member_live_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="POST"
REQ_PATH="/v1/esims/$ID/refund-requests"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"reason":"<reason>"}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "decided_at": "2026-07-11T08:12:30Z",
    "decision_note": "<decision_note>",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "mode": "live",
    "order_id": "018f2b00-0000-7000-8000-00000000009a",
    "order_item_id": "018f2b00-0000-7000-8000-000000000000",
    "reason": "<reason>",
    "refunded": null,
    "requested_at": "2026-07-11T08:12:30Z",
    "status": "pending",
    "suggested": {
      "amount": 273000,
      "currency": "VND"
    }
  },
  "ok": true
}

Пример тела запроса

json
{
  "reason": "<reason>"
}
GET/v1/esims/{id}/usage

Использованные данные

scope esims:readget_esims__id__usage

Требуемая область доступа: esims:read.

Использованные и оставшиеся данные eSIM с updated_at, показывающим фактическую свежесть. Для поставщиков с опросом по запросу (сейчас SkyFi) вызов ставит опрос в очередь и ЖДЁТ до 15 секунд. Успешный ответ содержит свежий результат. При ошибке, интервале менее 15 секунд между опросами одной eSIM или тайм-ауте ошибки НЕТ: возвращаются последние сохранённые данные, проверяйте updated_at. Другие поставщики возвращают данные периодического обновления.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
esim_idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
usageobject
допускает null

Статистика использованных данных eSIM. updated_at = null, если опроса ещё не было.

expires_atstring · date-time
допускает null
—
remaining_bytesinteger · int64
допускает null
—
sourcestring
допускает null
—
pollwebhook
total_bytesinteger · int64
допускает null
—
updated_atstring · date-time
допускает null
—
used_bytesinteger · int64
допускает null
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
422
capability.not_supported
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="GET"
REQ_PATH="/v1/esims/$ID/usage"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "esim_id": "018f2b00-0000-7000-8000-000000000000",
    "mode": "live",
    "usage": {
      "expires_at": "2026-08-10T00:00:00Z",
      "remaining_bytes": 5368709120,
      "source": "poll",
      "total_bytes": 5368709120,
      "updated_at": "2026-07-11T09:03:11Z",
      "used_bytes": 5368709120
    }
  },
  "ok": true
}

Кошелёк

Баланс кошелька

GET/v1/wallet/balance

Баланс кошелька

scope wallet:readget_wallet_balance

Требуемая область доступа: wallet:read.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
availableobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
balanceobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
credit_limitobject

Денежная сумма. ЕДИНЫЙ формат во всём /v1: поле не меняет тип между объектом и числом.

amountinteger · int64

Целое число в минимальной единице currency (у VND нет дробной единицы).

currencystring
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="GET"
REQ_PATH="/v1/wallet/balance"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "available": {
      "amount": 273000,
      "currency": "VND"
    },
    "balance": {
      "amount": 273000,
      "currency": "VND"
    },
    "credit_limit": {
      "amount": 273000,
      "currency": "VND"
    },
    "mode": "live"
  },
  "ok": true
}

Вебхуки

Настройки вебхуков, журнал доставок и симулятор

POST/v1/testmode/webhooks/fail-next

Симулировать следующую ошибку вебхука (только тестовые ключи)

scope webhook:managepost_testmode_webhooks_fail-next

Требуемая область доступа: webhook:manage.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
countinteger
—

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
endpoint_idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
testmode_fail_nextinteger
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbidden
404
internal.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="POST"
REQ_PATH="/v1/testmode/webhooks/fail-next"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"count":1}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "endpoint_id": "018f2b00-0000-7000-8000-000000000000",
    "mode": "live",
    "testmode_fail_next": 1
  },
  "ok": true
}

Пример тела запроса

json
{
  "count": 1
}
POST/v1/testmode/webhooks/fire

Отправить тестовое событие вебхука (только тестовые ключи)

scope webhook:managepost_testmode_webhooks_fire

Требуемая область доступа: webhook:manage.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
dataobject

Частичное переопределение поля data тестового содержимого.

event_typestring
обязательно

Тип тестового события вебхука.

order.completedorder.failedesim.deliveredesim.activatedesim.usage_thresholdesim.expiringwallet.low_balancecatalog.updated

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
delivery_idstring · uuid
—
event_idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbidden
404
webhook.not_configuredinternal.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="POST"
REQ_PATH="/v1/testmode/webhooks/fire"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"data":{},"event_type":"order.completed"}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "delivery_id": "018f2b00-0000-7000-8000-000000000000",
    "event_id": "018f3c00-0000-7000-8000-0000000000b0",
    "mode": "live"
  },
  "ok": true
}

Пример тела запроса

json
{
  "data": {},
  "event_type": "order.completed"
}
GET/v1/webhooks

Список обработчиков вебхуков

scope webhook:manageget_webhooks

Требуемая область доступа: webhook:manage.

Обработчики вебхуков текущего API-ключа. Ключ НИКОГДА не видит обработчики другого, даже в одной организации (§5.11, привязка к api_key_id; тестовый и рабочий ключи разделены). Если настройка ещё не создана, возвращается 200 с ПУСТЫМ массивом, не 404.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
itemsobject[]
—
activeboolean

false — обработчик перестал получать новые события после паузы в панели или отзыва API-ключа (канал закрывается той же транзакцией). Успешная CHECK_HEALTH при PUT /v1/webhooks/{id} включает его снова.

created_atstring · date-time
—
eventsstring[]

Пусто — получать все события.

idstring · uuid

ID обработчика для /v1/webhooks/{id}. При каждом создании выдаётся НОВЫЙ: удаление и повторное создание не восстанавливают старый обработчик.

last_delivery_atstring · date-time
допускает null

Последнее время УСПЕШНОЙ ДОСТАВКИ обработчику. null — доставок не было, а не ошибка доставки. Проверьте первым при жалобе на отсутствие вебхуков.

secret_last4string

Последние четыре символа секрета вебхука для проверки соответствия без раскрытия секрета. Полный секрет показывается ОДИН РАЗ при создании.

urlstring
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_cursorstring
допускает null

Передайте БЕЗ ИЗМЕНЕНИЙ для следующей страницы. null — БОЛЬШЕ ДАННЫХ НЕТ, единственный сигнал остановки: за страницей короче page_size могут оставаться другие.

page_sizeinteger
допускает null

МАКСИМАЛЬНОЕ число записей, использованное сервером для страницы с учётом ограничения, а не фактическая длина items.

totalinteger · int64
допускает null

Число строк по фильтру. null, если подсчёт слишком дорог. Конец списка определяйте по next_cursor, а не по этому полю.

warningsobject[]

ПРИЧИНЫ состава списка. Пусто — особенностей нет. code — ЗАКРЫТЫЙ набор: filtered_by_key_package_filters (ограничения каталога ключа), filtered_by_provider_scope (поставщики организации), no_price_for_org (нет цены), price_filter_removed_rows (фильтр цены), provider_disabled (нет доступного поставщика), capability_missing (не заявлены действия после продажи). count — число затронутых строк или null, если не выразимо числом.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
допускает null
—
messagestring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="GET"
REQ_PATH="/v1/webhooks"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "items": [
      {
        "active": true,
        "created_at": "2026-07-11T08:12:30Z",
        "events": [
          "order.completed"
        ],
        "id": "018f2b00-0000-7000-8000-00000000009a",
        "last_delivery_at": "2026-07-11T08:12:30Z",
        "secret_last4": "a3f9",
        "url": "https://partner.example.com/hooks/tke"
      }
    ],
    "mode": "live",
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNy0xMVQwODoxMjozMFoifQ",
    "page_size": 50,
    "total": 128,
    "warnings": []
  },
  "ok": true
}
POST/v1/webhooks

Создать обработчик вебхука

scope webhook:managepost_webhooks

Требуемая область доступа: webhook:manage.

Создать обработчик вебхука для текущего API-ключа. До сохранения сервер отправляет подписанный ping на указанный URL (CHECK_HEALTH). Без 2xx ничего не сохраняется: 422 webhook.health_check_failed. URL — публичный https; внутренний или metadata IP даёт 422 webhook.url_not_allowed. Ответ показывает webhook_secret ровно один раз, прочитать его снова нельзя. При потере смените секрет в панели; старый действует ещё 24 часа (§8.4). Если у ключа уже есть обработчик, даже приостановленный (is_active: false), возвращается 409 webhook.already_configured: URL, секрет и журнал сохраняются. Чтобы начать заново, вызовите DELETE /v1/webhooks/{id} и создайте новый.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
eventsstring[]

Пусто — получать все события. Неизвестное значение: 400 internal.validation_failed (поле events).

urlstring · uri
обязательно
—

Ответ

201Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
activeboolean

false — обработчик перестал получать новые события после паузы в панели или отзыва API-ключа (канал закрывается той же транзакцией). Успешная CHECK_HEALTH при PUT /v1/webhooks/{id} включает его снова.

created_atstring · date-time
—
eventsstring[]

Пусто — получать все события.

health_checkstring
—
passed
idstring · uuid

ID обработчика для /v1/webhooks/{id}. При каждом создании выдаётся НОВЫЙ: удаление и повторное создание не восстанавливают старый обработчик.

last_delivery_atstring · date-time
допускает null

Последнее время УСПЕШНОЙ ДОСТАВКИ обработчику. null — доставок не было, а не ошибка доставки. Проверьте первым при жалобе на отсутствие вебхуков.

modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
secret_last4string

Последние четыре символа секрета вебхука для проверки соответствия без раскрытия секрета. Полный секрет показывается ОДИН РАЗ при создании.

urlstring
—
webhook_secretstring

Показывается ТОЛЬКО ОДИН РАЗ при создании (префикс whsec_).

okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
409
webhook.already_configured
422
webhook.health_check_failedwebhook.url_not_allowed
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="POST"
REQ_PATH="/v1/webhooks"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"events":["order.completed"],"url":"https://partner.example.com/hooks/tke"}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "active": true,
    "created_at": "2026-07-11T08:12:30Z",
    "events": [
      "order.completed"
    ],
    "health_check": "passed",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "last_delivery_at": "2026-07-11T08:12:30Z",
    "mode": "live",
    "secret_last4": "a3f9",
    "url": "https://partner.example.com/hooks/tke",
    "webhook_secret": "<webhook secret — chỉ hiện đúng một lần>"
  },
  "ok": true
}

Пример тела запроса

json
{
  "events": [
    "order.completed"
  ],
  "url": "https://partner.example.com/hooks/tke"
}
GET/v1/webhooks/{id}

Получить один обработчик вебхука

scope webhook:manageget_webhooks__id_

Требуемая область доступа: webhook:manage.

Параметры пути (1)

idstringобязательно

ID обработчика вебхука: id из POST /v1/webhooks или GET /v1/webhooks.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
activeboolean

false — обработчик перестал получать новые события после паузы в панели или отзыва API-ключа (канал закрывается той же транзакцией). Успешная CHECK_HEALTH при PUT /v1/webhooks/{id} включает его снова.

created_atstring · date-time
—
eventsstring[]

Пусто — получать все события.

idstring · uuid

ID обработчика для /v1/webhooks/{id}. При каждом создании выдаётся НОВЫЙ: удаление и повторное создание не восстанавливают старый обработчик.

last_delivery_atstring · date-time
допускает null

Последнее время УСПЕШНОЙ ДОСТАВКИ обработчику. null — доставок не было, а не ошибка доставки. Проверьте первым при жалобе на отсутствие вебхуков.

modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
secret_last4string

Последние четыре символа секрета вебхука для проверки соответствия без раскрытия секрета. Полный секрет показывается ОДИН РАЗ при создании.

urlstring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
webhook.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="GET"
REQ_PATH="/v1/webhooks/$ID"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "active": true,
    "created_at": "2026-07-11T08:12:30Z",
    "events": [
      "order.completed"
    ],
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "last_delivery_at": "2026-07-11T08:12:30Z",
    "mode": "live",
    "secret_last4": "a3f9",
    "url": "https://partner.example.com/hooks/tke"
  },
  "ok": true
}
PUT/v1/webhooks/{id}

Обновить обработчик вебхука

scope webhook:manageput_webhooks__id_

Требуемая область доступа: webhook:manage.

Параметры пути (1)

idstringобязательно

ID обработчика вебхука: id из POST /v1/webhooks или GET /v1/webhooks.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
eventsstring[]

Пусто — получать все события. Неизвестное значение: 400 internal.validation_failed (поле events).

urlstring · uri
обязательно
—

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
activeboolean

false — обработчик перестал получать новые события после паузы в панели или отзыва API-ключа (канал закрывается той же транзакцией). Успешная CHECK_HEALTH при PUT /v1/webhooks/{id} включает его снова.

created_atstring · date-time
—
eventsstring[]

Пусто — получать все события.

idstring · uuid

ID обработчика для /v1/webhooks/{id}. При каждом создании выдаётся НОВЫЙ: удаление и повторное создание не восстанавливают старый обработчик.

last_delivery_atstring · date-time
допускает null

Последнее время УСПЕШНОЙ ДОСТАВКИ обработчику. null — доставок не было, а не ошибка доставки. Проверьте первым при жалобе на отсутствие вебхуков.

modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
secret_last4string

Последние четыре символа секрета вебхука для проверки соответствия без раскрытия секрета. Полный секрет показывается ОДИН РАЗ при создании.

urlstring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
webhook.not_found
422
webhook.health_check_failedwebhook.url_not_allowed
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="PUT"
REQ_PATH="/v1/webhooks/$ID"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"events":["order.completed"],"url":"https://partner.example.com/hooks/tke"}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "active": true,
    "created_at": "2026-07-11T08:12:30Z",
    "events": [
      "order.completed"
    ],
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "last_delivery_at": "2026-07-11T08:12:30Z",
    "mode": "live",
    "secret_last4": "a3f9",
    "url": "https://partner.example.com/hooks/tke"
  },
  "ok": true
}

Пример тела запроса

json
{
  "events": [
    "order.completed"
  ],
  "url": "https://partner.example.com/hooks/tke"
}
DELETE/v1/webhooks/{id}

Удалить обработчик вебхука (прекратить новые события)

scope webhook:managedelete_webhooks__id_

Требуемая область доступа: webhook:manage.

Удалить обработчик и прекратить новые события текущего API-ключа. Теряется webhook_secret: повторный POST /v1/webhooks создаёт НОВЫЕ id и секрет, проверку подписи нужно обновить. Для временной остановки с сохранением секрета используйте кнопку паузы в панели. Сохраняется журнал для сверки, но переотправлять доставки удалённого обработчика нельзя. Ожидающие pending доставки останавливаются с endpoint_disabled и остаются в журнале (§8.5). Чужой или уже удалённый id даёт 404 webhook.not_found, не 200.

Параметры пути (1)

idstringобязательно

ID обработчика вебхука: id из POST /v1/webhooks или GET /v1/webhooks.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
deleted_atstring · date-time
—
idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
webhook.not_found
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="DELETE"
REQ_PATH="/v1/webhooks/$ID"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "deleted_at": "2026-07-11T08:12:30Z",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "mode": "live"
  },
  "ok": true
}
GET/v1/webhooks/deliveries

Журнал доставок вебхуков

scope webhook:manageget_webhooks_deliveries

Требуемая область доступа: webhook:manage.

Параметры запроса (5)

pageintegerнеобязательнопо умолчанию 1min 1

Номер страницы с 1. За концом списка возвращаются items: [] и реальный total (200, не 404).

page_sizeintegerнеобязательнопо умолчанию 50min 1max 200

Записей на странице. По умолчанию 50, максимум 200. Большее значение ограничивается 200, это НЕ ошибка.

event_typestringнеобязательно

Фильтр по типу события (см. каталог вебхуков).

statusstringнеобязательно
Допустимые значения:pendingdeliveredfailed

Фильтр по статусу доставки. ?status=failed используется для сверки вебхуков запросами (§13). Неизвестное значение не вызывает ошибку: фильтр ИГНОРИРУЕТСЯ, возвращается полный журнал.

event_idstring · uuidнеобязательно

Все попытки доставки ОДНОГО события, включая переотправки.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
itemsobject[]
—
attemptsinteger
—
created_atstring · date-time
—
event_idstring · uuid

Идентификатор СОБЫТИЯ. Переотправка создаёт НОВУЮ доставку с ТЕМ ЖЕ event_id; удаляйте дубли по event_id, не по id доставки.

event_typestring
—
idstring · uuid
—
last_attempt_atstring · date-time
допускает null
—
next_retry_atstring · date-time
допускает null
—
response_codeinteger
допускает null
—
statusstring
—
pendingdeliveredfailed
webhook_idstring · uuid
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_cursorstring
допускает null

Передайте БЕЗ ИЗМЕНЕНИЙ для следующей страницы. null — БОЛЬШЕ ДАННЫХ НЕТ, единственный сигнал остановки: за страницей короче page_size могут оставаться другие.

page_sizeinteger
допускает null

МАКСИМАЛЬНОЕ число записей, использованное сервером для страницы с учётом ограничения, а не фактическая длина items.

totalinteger · int64
допускает null

Число строк по фильтру. null, если подсчёт слишком дорог. Конец списка определяйте по next_cursor, а не по этому полю.

warningsobject[]

ПРИЧИНЫ состава списка. Пусто — особенностей нет. code — ЗАКРЫТЫЙ набор: filtered_by_key_package_filters (ограничения каталога ключа), filtered_by_provider_scope (поставщики организации), no_price_for_org (нет цены), price_filter_removed_rows (фильтр цены), provider_disabled (нет доступного поставщика), capability_missing (не заявлены действия после продажи). count — число затронутых строк или null, если не выразимо числом.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
допускает null
—
messagestring
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
webhook.not_configured
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="GET"
REQ_PATH="/v1/webhooks/deliveries"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "items": [
      {
        "attempts": 3,
        "created_at": "2026-07-11T08:12:30Z",
        "event_id": "018f3c00-0000-7000-8000-0000000000b0",
        "event_type": "order.completed",
        "id": "018f2b00-0000-7000-8000-00000000009a",
        "last_attempt_at": "2026-07-11T09:05:00Z",
        "next_retry_at": "2026-07-11T09:10:00Z",
        "response_code": 200,
        "status": "pending",
        "webhook_id": "018f2b00-0000-7000-8000-000000000000"
      }
    ],
    "mode": "live",
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNi0wNy0xMVQwODoxMjozMFoifQ",
    "page_size": 50,
    "total": 128,
    "warnings": []
  },
  "ok": true
}
POST/v1/webhooks/deliveries/{id}/replay

Повторно отправить вебхук

scope webhook:managepost_webhooks_deliveries__id__replay

Требуемая область доступа: webhook:manage.

Параметры пути (1)

idstringобязательно

ID одной доставки вебхука (items[].id из GET /v1/webhooks/deliveries).

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
attemptsinteger
—
created_atstring · date-time
—
event_idstring · uuid

Идентификатор СОБЫТИЯ. Переотправка создаёт НОВУЮ доставку с ТЕМ ЖЕ event_id; удаляйте дубли по event_id, не по id доставки.

event_typestring
—
idstring · uuid
—
last_attempt_atstring · date-time
допускает null
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
next_retry_atstring · date-time
допускает null
—
response_codeinteger
допускает null
—
statusstring
—
pendingdeliveredfailed
webhook_idstring · uuid
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspended
404
webhook.delivery_not_foundwebhook.not_configured
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="POST"
REQ_PATH="/v1/webhooks/deliveries/$ID/replay"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "attempts": 3,
    "created_at": "2026-07-11T08:12:30Z",
    "event_id": "018f3c00-0000-7000-8000-0000000000b0",
    "event_type": "order.completed",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "last_attempt_at": "2026-07-11T09:05:00Z",
    "mode": "live",
    "next_retry_at": "2026-07-11T09:10:00Z",
    "response_code": 200,
    "status": "pending",
    "webhook_id": "018f2b00-0000-7000-8000-000000000000"
  },
  "ok": true
}

Тестовый режим

Используйте только тестовые ключи tke_test_ или tke_member_test_ на хосте api-testing. Моделируются тестовые eSIM и данные владельца без реальных средств и поставщиков. Для участников не поддерживаются вебхуки, пакетные заказы и заявки на ручное рассмотрение возврата.

POST/v1/testmode/esims/{id}/simulate

Симулировать жизненный цикл тестовой eSIM (только тестовые ключи)

scope esims:writepost_testmode_esims__id__simulate

Требуемая область доступа: esims:write.

Только тестовые ключи tke_test_ или tke_member_test_. Задайте сценарий для тестовой eSIM владельца: расход, установку, истечение срока, ограничение скорости или отказ поставщика при следующей операции. В теле требуется хотя бы одно поле; пустое тело возвращает 422 testmode.invalid_scenario. Результат читается через GET /v1/esims/{id} и /usage, как в рабочем режиме. Правила смены плана и отмены общие; реальный поставщик не вызывается.

Параметры пути (1)

idstringобязательно

Постоянный ID проданной eSIM, НЕ ICCID, который может повторно использоваться.

Тело запросаобязательноapplication/json

ПолеТипОграниченияОписание
data_used_mbinteger · int64

УЖЕ ИСПОЛЬЗОВАННЫЕ данные в МБ (двоичные единицы 1 MiB). Значение выше лимита ограничивается лимитом: это сценарий исчерпания данных, а не ошибка.

expire_atstring · date-time

Задать точное время истечения срока (ISO-8601 UTC).

expire_nowboolean

true — немедленно завершить срок тарифа (esim_status = expired).

provider_rejectboolean

Симулировать отказ поставщика при следующем пополнении/отмене.

statusstring

ТЕХНИЧЕСКИЙ жизненный цикл профиля eSIM по терминологии поставщика, ОТЛИЧНЫЙ от коммерческого esim_status в методах eSIM.

releasedinstalledenableddisabled
throttledboolean

true — скорость ограничена после скоростного порога безлимитного тарифа.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
cancellableobject

Можно ли ещё отменить eSIM и почему нельзя. Прочитайте ДО POST /v1/esims/{order_item_id}/cancel. Блок присутствует ВСЕГДА и никогда не null.

allowedboolean
—
disclosed_atstring · date-time
допускает null

Время выдачи кода за пределы системы (ISO-8601 UTC), null, пока его никто не видел. Позволяет проверить жалобу «я не получил код, почему нельзя отменить».

reasonstring
допускает null

Причина запрета отмены: code_disclosed — код выдан или показан; wrong_status — неподходящее состояние; expired — срок истёк; not_supported — отмена не заявлена тарифом. null при allowed = true. Термины как в meta.reason ошибки esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
допускает null

Ваша ссылка заказа external_ref, переданная при покупке, для связи eSIM с вашей системой. null, если не указана.

iccidstring
допускает null
—
idstring · uuid

ID eSIM для всех путей /v1/esims/{id}/…, он же order_item_id вебхуков. Если известен только ICCID, получите id через GET /v1/esims?iccid=….

installobject
допускает null

Данные для УСТАНОВКИ eSIM. null означает, что вы их НЕ ЗАПРАШИВАЛИ (нет include=lpa), а не отсутствие кода. Если кода ещё нет, блок присутствует с lpa: null. Ответ с этим блоком всегда имеет Cache-Control: no-store.

android_urlstring
допускает null
—
apnstring
допускает null

APN для ручного ввода, если устройство не получает настройки автоматически. В текущей версии ВСЕГДА null, что означает «неизвестно», а не «не требуется».

ios_urlstring
допускает null
—
lpastring
допускает null

Код активации eSIM — секрет продажи. Этой строки достаточно для создания QR-кода у вас.

matching_idstring
допускает null
—
qr_code_base64string
допускает null

PNG data-URI QR-кода. Только при include=qr в ДЕТАЛЬНОМ методе. В списках его НЕТ (около 11 КБ на строку, то есть 1,1 МБ на 100 строк); создавайте QR из lpa.

smdp_addressstring
допускает null
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
msisdnstring
допускает null
—
order_idstring · uuid
—
package_codestring
допускает null

КОД тарифа из каталога для связи eSIM с вашим каталогом. null только если исходный тариф удалён из каталога.

package_namestring

Отображаемое НАЗВАНИЕ может меняться. Не используйте его как ключ сверки.

planobject
допускает null

Текущий тариф этой eSIM.

current_codestring
—
expires_atstring · date-time
допускает null
—
started_atstring · date-time
допускает null
—
simulatedobject

Состояние ПОСЛЕ применения сценария.

esim_statestring

ТЕХНИЧЕСКИЙ цикл тестовой eSIM. У рабочей eSIM он берётся из smdp_status, но у тестовой нет профиля SM-DP+, поэтому поле хранится среди тестовых данных.

releasedinstalledenableddisabled
last_viastring

Процесс, ПОСЛЕДНИМ ИЗМЕНИВШИЙ её.

open_apisimulateseed
viastring

Процесс, СОЗДАВШИЙ тестовую eSIM.

open_apisimulateseed
smdp_statusstring
допускает null
—
statusstring
—
updated_atstring · date-time
—
usageobject
допускает null

Статистика использованных данных eSIM. updated_at = null, если опроса ещё не было.

expires_atstring · date-time
допускает null
—
remaining_bytesinteger · int64
допускает null
—
sourcestring
допускает null
—
pollwebhook
total_bytesinteger · int64
допускает null
—
updated_atstring · date-time
допускает null
—
used_bytesinteger · int64
допускает null
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbidden
404
internal.not_found
422
testmode.invalid_scenario
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"
ID="THAY-BANG-ID"

METHOD="POST"
REQ_PATH="/v1/testmode/esims/$ID/simulate"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"data_used_mb":4096,"expire_at":"2026-07-11T08:12:30Z","expire_now":true,"provider_reject":true,"status":"enabled","throttled":true}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "cancellable": {
      "allowed": true,
      "disclosed_at": "2026-07-11T08:12:30Z",
      "reason": "code_disclosed"
    },
    "external_ref": "po-1001",
    "iccid": "8984012345678901234",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "install": {
      "android_url": "https://esimsetup.google.com/esim_qrcode_provisioning?data=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
      "apn": "<apn>",
      "ios_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24smdp.example.com%24MATCH-ABC123",
      "lpa": "LPA:1$smdp.example.com$MATCH-ABC123",
      "matching_id": "MATCH-ABC123",
      "qr_code_base64": "<PNG base64 — đã rút gọn>",
      "smdp_address": "smdp.example.com"
    },
    "mode": "live",
    "msisdn": "+84900000000",
    "order_id": "018f2b00-0000-7000-8000-00000000009a",
    "package_code": "TKE-VN-30D-X3K9F",
    "package_name": "Tên gói hiển thị",
    "plan": {
      "current_code": "TKE-VN-30D-X3K9F",
      "expires_at": "2026-08-10T00:00:00Z",
      "started_at": "2026-07-11T08:12:30Z"
    },
    "simulated": {
      "esim_state": "released",
      "last_via": "open_api",
      "via": "open_api"
    },
    "smdp_status": "released",
    "status": "delivered",
    "updated_at": "2026-07-11T09:03:11Z",
    "usage": {
      "expires_at": "2026-08-10T00:00:00Z",
      "remaining_bytes": 5368709120,
      "source": "poll",
      "total_bytes": 5368709120,
      "updated_at": "2026-07-11T09:03:11Z",
      "used_bytes": 5368709120
    }
  },
  "ok": true
}

Пример тела запроса

json
{
  "data_used_mb": 4096,
  "expire_at": "2026-07-11T08:12:30Z",
  "expire_now": true,
  "provider_reject": true,
  "status": "enabled",
  "throttled": true
}
POST/v1/testmode/reset

Сбросить тестовые данные (только тестовые ключи)

scope orders:writepost_testmode_reset

Требуемая область доступа: orders:write.

Заголовки (1)

Idempotency-Keystringнеобязательно

Тело запросанеобязательноapplication/json

ПолеТипОграниченияОписание
reasonstring
—

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
esim_states_deletedinteger · int64
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
order_items_deletedinteger · int64
—
orders_deletedinteger · int64
—
wallet_balanceinteger · int64

Баланс тестового кошелька ПОСЛЕ сброса, всегда 0. Реестр НЕ удаляется: обнуление выполняется компенсирующей проводкой.

okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbidden
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="POST"
REQ_PATH="/v1/testmode/reset"   # them query thi noi thang vao day TRUOC khi ky
BODY='{"reason":"Reset test data"}'

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

Пример ответа

json
{
  "data": {
    "esim_states_deleted": 1,
    "mode": "live",
    "order_items_deleted": 1,
    "orders_deleted": 1,
    "wallet_balance": 1
  },
  "ok": true
}

Пример тела запроса

json
{
  "reason": "Reset test data"
}
POST/v1/testmode/seed

Создать примеры тестовых данных (только тестовые ключи)

scope orders:writepost_testmode_seed

Требуемая область доступа: orders:write.

Заголовки (1)

Idempotency-Keystringнеобязательно

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
already_seededboolean
—
esims_createdinteger
—
modestring

Среда вызывающего ключа, соответствующая заголовку X-TKE-Mode. Организации используют tke_live_/tke_test_, участники — tke_member_live_/tke_member_test_. Рабочий и тестовый хосты проверяют режим ключа.

testlive
orders_createdinteger
—
seededboolean
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbidden
422
testmode.no_sellable_package
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="POST"
REQ_PATH="/v1/testmode/seed"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG" \
  -H "Idempotency-Key: $(uuidgen)"

Пример ответа

json
{
  "data": {
    "already_seeded": true,
    "esims_created": 1,
    "mode": "live",
    "orders_created": 1,
    "seeded": true
  },
  "ok": true
}
GET/v1/testmode/status

Обзор тестового режима (только тестовые ключи)

scope esims:readget_testmode_status

Требуемая область доступа: esims:read.

Ответ

200Успешно

Успешно

ПолеТипОграниченияОписание
dataobject
обязательно
—
base_url_testingstring

Хост тестового режима. Используйте только ключи tke_test_ или tke_member_test_; обращение к рабочему хосту возвращает 403 partner.wrong_host.

capabilitiesobject
—
bulk_ordersboolean
—
cancel_refundboolean
—
refund_requestsboolean
—
webhooksboolean
—
checklistobject[]

Этапы интеграции определяются по реальным вызовам API того же владельца в тестовом режиме. Проверка вебхуков применяется только к организациям.

doneboolean
—
labelstring
—
last_atstring · date-time
допускает null
—
stepstring
—
driftobject

Цикл имитации расхода: включён ли он, интервал и последний запуск. Вебхуки порогов отправляются только организациям с поддержкой вебхуков.

enabledboolean
—
interval_secsinteger · int64
—
last_run_atstring · date-time
допускает null
—
modestring
—
testlive
readyboolean

Выполнены ли владельцем ключа все поддерживаемые пункты проверки. Участникам не требуется проверка вебхуков.

sample_countsobject
—
test_esimsinteger · int64
—
test_ordersinteger · int64
—
seededboolean

Есть ли у владельца ключа образцы тестовых заказов и eSIM.

test_key_countinteger · int64
—
wallet_balanceinteger · int64

Баланс тестового кошелька владельца ключа в виртуальных VND, изолированный от рабочего кошелька.

webhookobject
—
endpoint_urlstring
допускает null
—
fail_nextinteger
—
statusstring
допускает null
—
okboolean
обязательно
—
true

Ответы с ошибкой

Все ошибки возвращаются в формате { ok: false, error } с соответствующим HTTP-статусом, никогда с 200. Описания кодов есть на странице ошибок.

400
internal.validation_failed
401
partner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reused
403
partner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbidden
429
partner.rate_limited
503
partner.endpoint_disabledinternal.service_unavailable

Пример вызова

Все значения TKE-VN-30D-X3K9F нужно заменить своими. Реальные коды тарифов получите через GET /v1/catalog/packages.

bash
#!/usr/bin/env bash
# Can: bash + openssl + uuidgen.

KEY_ID="tke_test_THAY-BANG-KEY-ID-CUA-BAN"
SECRET="THAY-BANG-SECRET-CUA-BAN"

METHOD="GET"
REQ_PATH="/v1/testmode/status"   # them query thi noi thang vao day TRUOC khi ky
BODY=""

TS="$(date +%s000)"                 # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"

curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
  -H "Accept-Language: ru" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Пример ответа

json
{
  "data": {
    "base_url_testing": "<base_url_testing>",
    "capabilities": {
      "bulk_orders": true,
      "cancel_refund": true,
      "refund_requests": true,
      "webhooks": true
    },
    "checklist": [
      {
        "done": true,
        "label": "<label>",
        "last_at": "2026-07-11T08:12:30Z",
        "step": "<step>"
      }
    ],
    "drift": {
      "enabled": true,
      "interval_secs": 1,
      "last_run_at": "2026-07-11T08:12:30Z"
    },
    "mode": "test",
    "ready": true,
    "sample_counts": {
      "test_esims": 1,
      "test_orders": 1
    },
    "seeded": true,
    "test_key_count": 1,
    "wallet_balance": 1,
    "webhook": {
      "endpoint_url": "https://partner.example.com/hooks/tke",
      "fail_next": 1,
      "status": "completed"
    }
  },
  "ok": true
}