Tổng Kho eSIM
OpenAPI 2.4.0 · 30 Operationen

API-Referenz

Alle /v1-Endpunkte aus der Backend-Spezifikation: Pfade, Parameter, Schemas, Enums und Fehler entsprechen dem Server.

Live

https://api.tongkhoesim.com/v1

Testmodus

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

Produktiv: api.tongkhoesim.com / tke_live_ (Partner) oder tke_member_live_ (freigeschaltete Mitglieder). Test: api-testing.tongkhoesim.com / tke_test_ / tke_member_test_ (Partner / Mitglieder).

Allgemeine Konventionen

Diese 6 Regeln gelten für alle 30 Operationen.

Authentifizierung

Alle Endpunkte einschließlich GET: HMAC-SHA256 mit 4 Headern: X-TKE-Key · X-TKE-Timestamp · X-TKE-Nonce · X-TKE-Signature. Kein Zugriff ohne Signatur.

Antwortformat
Erfolg: { ok: true, data }. Fehler: { ok: false, error: { code, message, field?, meta?, request_id } }. Passender HTTP-Status, niemals 200 bei Fehlern.
Datentypen
Geldbeträge als ganze VND (BIGINT). Datenmengen in Bytes, auch im Katalog (data.total_bytes / data.daily_bytes). Zeitangaben ISO-8601 UTC, für die Anzeige lokal umrechnen.
Ratenbegrenzung
429 enthält Retry-After und X-RateLimit-Limit / Remaining / Reset. Lesen Sie diese Header; Grenzen gelten pro Schlüssel.
request_id
Jede Antwort enthält X-Request-Id, bei Fehlern auch error.request_id. Für Supportanfragen immer protokollieren.
Idempotency-Key

Gleicher Schlüssel und Body innerhalb 24 h: Ergebnis ohne erneute Abbuchung wiedergegeben. Pro api_key_id. Pflicht bei 5 Operationen: POST /v1/orders, POST /v1/orders/bulk, POST /v1/esims/{id}/cancel, POST /v1/esims/{id}/change-plan, POST /v1/esims/{id}/refund-requests.

Seitennavigation: zwei Modelle

Formate und Abbruchbedingungen unterscheiden sich. Behandeln Sie beide Modelle getrennt.

Offset · page / page_size

Nur GET /v1/webhooks/deliveries. Liefert page, page_size und total.

Außerhalb des Bereichs: HTTP 200, items: [] und echtes total. Bei leerer Liste oder erreichtem total stoppen.

Cursor · cursor / next_cursor

Für /v1/catalog/packages, /v1/orders, /v1/esims und /v1/esims/{id}/plan-options. updated_since ist nur bei orders und esims möglich.

Stoppen, wenn next_cursor fehlt oder dem aktuellen Cursor entspricht. Das Feld kann in data ganz fehlen.

Die Falle ungültiger Cursor mit HTTP 200

Ungültige Cursor beginnen für ältere Clients ohne Fehler von vorn. Eine Prüfung nur auf next_cursor !== null kann die erste Seite endlos wiederholen.

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

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

  // callSigned ergänzt Ihre 4 X-TKE-*-Header (HMAC-Seite).
  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;
}

Katalog

Paketkatalog mit EINER Oberfläche für beide Versorgungssysteme. Das System ist ein FELD (`market`: `domestic` | `international`) und Filter (`?market=`), nicht zwei getrennte Pfade. KEINE Bestandsangaben: keine Menge oder Stufe `available`/`low`/`out_of_stock`. Pakete erscheinen einfach; bei fehlender Lieferfähigkeit liefert `POST /v1/orders` einen klaren Fehler (`esim.out_of_stock`…), statt vorab im Katalog zu warnen. Einheitliche Cursor-Paginierung. `total` nur bei günstigem Zählen (Filter auf Inland), sonst `null`.

GET/v1/catalog/packages

Pakete auflisten

scope catalog:readget_catalog_packages

Erforderlicher Berechtigungsbereich: catalog:read.

Paketkatalog Ihrer Organisation: eine gemeinsame Oberfläche für beide Versorgungssysteme, gefiltert mit ?market=.

Es erscheinen nur tatsächlich KAUFBARE Pakete: veröffentlicht, weiterhin beim Anbieter vertrieben, durch den Schlüssel erlaubt UND für Ihre Organisation bepreist. Pakete ohne Preis fehlen, weil sie nicht bestellt werden können.

Cursor-Paginierung: zuerst ohne cursor aufrufen, dann next_cursor unverändert zurückgeben, bis es null ist. Eine kürzere Seite als page_size bedeutet NICHT das Ende.

capabilities stammt aus echten Paketangaben; fehlen diese, sind alle Flags false. Ein möglicher Button wird eher weggelassen, als ein sicher mit 422 scheiternder Endpunkt versprochen.

Der Katalog enthält KEINE Bestandsangaben, weder Mengen noch verfügbar/ausverkauft. Kann der Anbieter nicht liefern, meldet POST /v1/orders unmittelbar einen klaren Fehler (esim.out_of_stock…), statt eine Vorabinterpretation eines Feldes zu verlangen.

Query-Parameter (18)

page_sizeintegeroptionalStandard 50min 1max 100

Datensätze pro Seite. Standard 50, höchstens 100. Höhere Werte werden ohne Fehler auf 100 begrenzt.

marketstringoptional

Versorgungssystem: domestic | international. Unbekannte Werte führen zu internal.invalid_filter (400), nicht zum vollständigen Katalog. Ein Tippfehler darf nicht als wirkungsloser Filter erscheinen.

countrystringoptional

Ländercode nach ISO-3166 alpha-2 (z. B. VN). Nur Pakete mit Abdeckung dieses Landes.

regionstringoptional

Regionskennung (z. B. chau-a). Pakete mit mindestens einem Land dieser Region sowie alle globalen Pakete.

qstringoptional

Freitextsuche nach öffentlichem Code und Paketname (Teiltreffer, ohne Beachtung der Groß-/Kleinschreibung).

tagstringoptional

Nach EINEM kommerziellen Tag filtern. Überflüssige Leerzeichen werden entfernt, Kleinbuchstaben und Bindestriche verwendet: Ban Le und ban-le sind derselbe Tag.

data_modelstringoptional

Datenmodell: daily | fixed | unlimited, ersetzt supports_topup. Unbekannte Werte: internal.invalid_filter (400).

data_min_bytesinteger · int64optional

Mindestdatenvolumen in BYTES. Tagespakete werden anhand ihres TAGESVOLUMENS verglichen; unbegrenzte Pakete erfüllen IMMER eine Untergrenze.

data_max_bytesinteger · int64optional

Maximales Datenvolumen in BYTES. Unbegrenzte Pakete sind AUSGESCHLOSSEN, da eine Obergrenze für unbegrenzte Pakete nicht gilt und sonst bei der Suche nach kleinen Paketen erscheinen würde.

days_mininteger · int64optional

Mindestgültigkeitsdauer in Tagen.

days_maxinteger · int64optional

Höchste Gültigkeitsdauer in Tagen.

price_mininteger · int64optional

Mindestpreis (VND), verglichen mit dem Preis IHRER ORGANISATION. Der Filter wird NACH der serverseitigen Seitenauswahl angewendet. Kürzere Seiten bedeuten daher nicht das Datenende.

price_maxinteger · int64optional

Höchstpreis (VND), gleiche Regeln wie bei price_min.

network_genstringoptional

Nur Pakete dieser Netzgeneration (2g | 3g | 4g | 5g). Unbekannte Werte führen zu internal.invalid_filter (400).

hotspotstringoptional
Erlaubte Werte:truefalse

true: Tethering ausdrücklich unterstützt; false: ausdrücklich nicht unterstützt. Ohne Angabe (features.hotspot = null) passt keines von beiden. Nur die Zeichenfolgen true und false sind erlaubt.

appstringoptional

Nur Pakete, die diese App auf mindestens einem Betriebssystem AUSDRÜCKLICH unterstützen (app=tiktok). Fehlende Angaben erfüllen den Filter NICHT. Derzeit ist nur tiktok zulässig; andere Codes führen zu internal.invalid_filter (400).

voicestringoptional
Erlaubte Werte:truefalse

true: Telefonie ausdrücklich unterstützt; false: ausdrücklich nicht unterstützt. Ohne Angabe passt keines von beiden. Nur die Zeichenfolgen true und false sind erlaubt.

cursorstringoptional

Senden Sie next_cursor unverändert zurück. Stoppen Sie nur bei null, nicht bei kurzen Seiten: Schlüsselregeln, Organisationspreise und Preisfilter greifen nach dem Seitenschnitt. Der Cursor enthält den Filterfingerabdruck: Andere Filter oder beschädigte Daten ergeben catalog.cursor_mismatch (400), damit verschiedene Abfragen nicht vermischt werden.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
itemsobject[]
—
capabilitiesobject

Nachverkaufsfunktionen aus der tatsächlichen Paketdeklaration, nie vom Anbietertyp abgeleitet. Ohne Angaben sind alle Werte false, um keine Aktion zu versprechen, die sicher 422 capability.not_supported ergibt.

cancelobject
—
restrictionstring
kann null sein

code_not_disclosed: Stornierung nur, wenn niemand den Code gesehen hat (weder Kunde noch Dashboard noch ?include=lpa). Das ist die echte Bedingung, unabhängig von einer Installation, die das System nicht kennt. Die alte Bezeichnung unused_only war irreführend.

code_not_disclosednull
supportedboolean
—
plan_changeobject
—
policyobject
kann null sein

Verhalten des Anbieters beim AUFLADEN EINES NEUEN PAKETS. null bedeutet: nicht aufladbar ODER Anbieterrichtlinie noch nicht ermittelt. null verbietet Annahmen; ein Objekt nur mit false würde dagegen garantieren, dass nichts storniert wird.

……

Tiefer verschachtelte Felder: siehe JSON-Beispiel.

supportedboolean
—
usage_checkobject
—
supportedboolean
—
codestring

Paketcode aus IHREM Katalog, also public_sku. Pro Paket UNVERÄNDERLICH, auch bei Anbieterwechsel, daher als Abgleichschlüssel geeignet. Der Beispielwert hat das richtige Format, funktioniert aber NICHT; echten Code aus GET /v1/catalog/packages abrufen.

coverageobject

Nutzungsgebiete des Pakets.

countriesstring[]

ISO-3166-Alpha-2-Codes. Leer für globale Pakete (keine Aufzählung von 200 Ländern) und Pakete ohne deklarierte Abdeckung.

country_countinteger

Anzahl abgedeckter Länder, um Mehrländerpakete ohne vollständigen Abruf und eigenes Zählen zu sortieren oder zu filtern.

groupstring

Abdeckung: Länder (country), Region (region) oder weltweit (global). Offene Aufzählung: Unbekannte Werte ignorieren.

regionstring
kann null sein

Immer null: Regionen gehören zu Ländern, nicht zu Paketen. Der Filter ?region= funktioniert weiterhin.

dataobject

Datenvolumen des Pakets. Alle Werte in BYTES, nicht MB/GB, um doppelte Rundung zu vermeiden.

daily_bytesinteger · int64
kann null sein

TAGESDATENVOLUMEN in Bytes. null bei fixed- oder unlimited-Paketen.

labelstring

Lesbare Bezeichnung aus derselben Funktion wie auf allen unseren Oberflächen.

modelstring

Berechnungsart: daily (Tagesvolumen), fixed (Gesamtvolumen im Zeitraum), unlimited. OFFENE ENUMERATION: Clients MÜSSEN unbekannte Werte ignorieren.

dailyfixedunlimited
resetobject
kann null sein

Rücksetzzeit des Tagesvolumens, wichtig beim Kauf spät am Tag. null = nicht angegeben.

atstring
kann null sein

Zurücksetzzeit in der lokalen Zeitzone timezone (00:00, 00:00:01).

notestring
kann null sein
—
timezonestring
kann null sein

Zeitzone der Rücksetzung (UTC+8, UTC+7).

total_bytesinteger · int64
kann null sein

Gesamtvolumen des Zeitraums in Bytes. null für daily oder unlimited: Tagesvolumen mal Tage ist keine vom Anbieter zugesicherte Gesamtmenge.

descriptionstring
kann null sein

Verkaufsbeschreibung des Pakets. null, wenn noch nicht verfasst.

description_enstring
kann null sein

Englische Beschreibung; null, wenn nicht angegeben.

featuresobject

Paketfunktionen als typisiertes Objekt statt Codeliste: false und null unterscheiden fehlendes Tethering von unbekannter Information; [] kann das nicht.

appsobject[]

App-Unterstützung nach BETRIEBSSYSTEM.

Eine NICHT im Array vorhandene App bedeutet UNBEKANNT, nicht „funktioniert“. Nur gelistete Einträge sind Anbieterzusagen; derzeit ist nur TikTok angegeben.

Es gibt bewusst kein zusammengefasstes Feld: Im echten Katalog unterstützen 2.465 Pakete TikTok auf dem iPhone, aber NICHT auf Android. Ein einziges Flag würde sie auf „nein“ reduzieren und fast ein Drittel des Katalogs falsch darstellen.

androidstring

supported (Unterstützung erklärt), blocked (Nichtunterstützung erklärt), unknown (Betriebssystem nicht abgedeckt). Offene Aufzählung.

supportedblockedunknown
appstring

App-Code in Kleinbuchstaben (z. B. tiktok).

iosstring

supported (Unterstützung erklärt), blocked (Nichtunterstützung erklärt), unknown (Betriebssystem nicht abgedeckt). Offene Aufzählung.

supportedblockedunknown
notestring
kann null sein
—
hotspotboolean
kann null sein

WLAN-Tethering für andere Geräte. null = unbekannt, nicht false.

kyc_requiredboolean
kann null sein

Vor Nutzung ist eine Identitätsprüfung (KYC) erforderlich. null = nicht angegeben.

smsboolean
kann null sein

SMS. false bedeutet KEIN SMS-Empfang, somit auch keine Bank-OTPs. null = nicht angegeben.

voiceboolean
kann null sein

Sprachanrufe. false bedeutet ausdrücklich keine Telefonie: Eine reine Datenleitung empfängt keine Anrufe. null = unbekannt.

fupobject
kann null sein

Verhalten nach Verbrauch des Hochgeschwindigkeitsvolumens. null = keine Drosselungsangabe.

behaviorstring
kann null sein

throttle (gedrosselt), cutoff (getrennt) oder null (nicht angegeben). Offene Aufzählung.

throttlecutoffnull
high_speed_bytesinteger · int64
kann null sein

Bytes bei voller Geschwindigkeit vor der Drosselung.

notestring
kann null sein
—
speed_kbpsinteger
kann null sein

Geschwindigkeit NACH Verbrauch des schnellen Datenvolumens (kbps). null bei Abschaltung oder fehlender Angabe. Nicht dasselbe wie network.speed_cap_kbps, das Limit des gesamten Pakets.

marketstring

VERSORGUNGSSYSTEM des Pakets, OFFENE Enumeration: unbekannte Werte ignorieren. Bestimmt Bestandsmodell und Nachverkaufsfunktionen. domestic verkauft Kits mit Aufladung, Stornierung und Verbrauchsabfrage; international wird bei Bedarf beim Anbieter bestellt, ohne Bestand.

domesticinternational
namestring
—
name_enstring
kann null sein

Englischer Name; null, wenn nicht angegeben.

networkobject

Netze, die das Paket nutzen kann.

apnstring
kann null sein

APN zur manuellen Eingabe auf bestimmten Android-Geräten ohne automatische Konfiguration. Für vollständige Installationshinweise und weniger Supportfälle erforderlich. null = nicht angegeben.

carriersobject[]

Mobilfunkbetreiber, deren Netze das Paket nutzt. eSIM-Eigenschaft (Funknetz), nicht der Lieferant.

namestring
—
generationsstring[]
kann null sein

Nutzbare Mobilfunkgenerationen (["4g", "5g"]). null = nicht angegeben, NICHT „nur 2G“.

speed_cap_kbpsinteger
kann null sein

Geschwindigkeitslimit für das GESAMTE Paket (kbps), anders als fup.speed_kbps nach Verbrauch des schnellen Volumens.

priceobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
tagsstring[]

Normalisierte kommerzielle Tags (Kleinbuchstaben, Bindestriche), auch Grundlage der Katalogbeschränkungen Ihres Schlüssels. Derzeit trägt kein Paket Tags; ?tag= liefert daher eine leere Liste, bis der Verkäufer Tags vergibt.

validityobject

Gültigkeitsdauer des Pakets und Beginn der Laufzeit.

daysinteger
—
install_by_daysinteger
kann null sein

Tage nach Kauf zum Scannen des QR-Codes bzw. Installieren der eSIM. null = nicht angegeben, NICHT „jederzeit installierbar“.

labelstring
—
reclaim_on_expiryboolean
kann null sein

Der Anbieter zieht das Abonnement bei Ablauf zurück; Aufladen und Tarifwechsel sind danach unmöglich. Wichtig bei Verlängerungen. null = nicht angegeben.

starts_whenstring
kann null sein

Beginn der Laufzeit: on_install (Installation), on_purchase (Kauf) oder null. Entscheidend für das Ablaufdatum: Bei null kein genaues Datum zusagen.

on_installon_purchasenull
unused_valid_daysinteger
kann null sein

Anzahl der Tage, die ein Paket ungenutzt aufbewahrt werden kann, bevor es abläuft. null = nicht angegeben, NICHT unbegrenzt. Bewusst getrennt von install_by_days: LAGERDAUER und INSTALLATIONSFRIST sind verschieden.

modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_cursorstring
kann null sein

Für die nächste Seite UNVERÄNDERT zurücksenden. null ist das EINZIGE Endsignal. Auch auf eine Seite mit weniger als page_size Einträgen können weitere folgen.

page_sizeinteger
kann null sein

MAXIMALE Datensatzanzahl, die der Server für diese Seite verwendet hat, bereits begrenzt. Nicht die tatsächliche Anzahl in items.

totalinteger · int64
kann null sein

Anzahl passender Pakete; null bei teurer Zählung (international oder ohne market). Vor Schlüsselbeschränkungen und Organisationspreisen berechnet: Katalogtreffer, nicht kaufbare Pakete.

warningsobject[]

Erklärung der Liste; leer ohne Besonderheiten. Geschlossene Codes: filtered_by_key_package_filters, filtered_by_provider_scope, no_price_for_org, price_filter_removed_rows, provider_disabled, capability_missing. Sie stehen für Schlüsselregeln, Anbieterumfang, fehlende Preise, Preisfilter, deaktivierte Anbieter oder fehlende Fähigkeiten. count ist die betroffene Zeilenzahl, sonst null.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
kann null sein
—
messagestring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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}

Ein Paket abrufen

scope catalog:readget_catalog_packages__code_

Erforderlicher Berechtigungsbereich: catalog:read.

Details GENAU EINES Pakets anhand des öffentlichen Codes. data enthält DIESELBEN FELDER wie ein Eintrag von GET /v1/catalog/packages, ergänzt um mode. Keine exklusiven Detailfelder; ein einziger Typ Package genügt.

Unsichtbare Codes führen zu partner.package_not_visible (404), unabhängig davon, ob der Code fehlt, außerhalb der Schlüsselbeschränkungen liegt, kein Organisationspreis vorliegt oder die Anbieterzuordnung fehlt. Unterschiedliche Fehler würden Fremden die Katalogaufzählung ermöglichen.

Pfadparameter (1)

codestringerforderlich

Paketcode, im Katalog als code (public_sku). Pro Paket UNVERÄNDERLICH, auch bei Anbieterwechsel, und als Abgleichschlüssel speicherbar. Außerhalb der Schlüsselbeschränkungen oder ohne Organisationspreis: 404 partner.package_not_visible, wie bei nicht vorhandenem Code.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

Bestellungen

Bestellungen aufgeben und abgleichen

GET/v1/orders

Bestellungen auflisten (abfragebasierter Abgleich)

scope orders:readget_orders

Erforderlicher Berechtigungsbereich: orders:read.

Query-Parameter (5)

page_sizeintegeroptionalStandard 50min 1max 200

Datensätze pro Seite. Standard 50, höchstens 200. Höhere Werte werden ohne Fehler auf 200 begrenzt.

statusstringoptional
Erlaubte Werte:completedpending_providerprovider_failedcancelled

Nach Bestellstatus filtern. Werte außerhalb der Liste führen zu internal.invalid_filter (400).

external_refstringoptional

Nach der beim Bestellabschluss gesendeten Referenz filtern (EXAKTE Übereinstimmung).

updated_sincestring · date-timeoptional

ISO-8601 UTC. Liefert alle Datensätze mit updated_at >= updated_since in aufsteigender Reihenfolge. Dies ist der Standardweg für ABFRAGEBASIERTEN ABGLEICH (§8.5), auch bei verpassten Webhooks. Falsches Format: internal.invalid_filter (400).

cursorstringoptional

Nächster Cursor: Senden Sie next_cursor unverändert zurück. Ein ungültiger Cursor beginnt ohne Fehler am Listenanfang, damit Änderungen der Codierung Integrationen nicht brechen. Prüfen Sie, ob der Cursor wechselt, um Endlosschleifen auf Seite eins zu vermeiden.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
itemsobject[]
—
created_atstring · date-time
—
external_refstring
kann null sein
—
fulfillment_statusstring
—
fulfilledpartially_fulfilledunfulfilled
idstring · uuid
—
item_countinteger · int64
—
netobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
public_refstring
kann null sein
—
refundedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
statusstring
—
totalobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
updated_atstring · date-time
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_cursorstring
kann null sein

Für die nächste Seite UNVERÄNDERT zurücksenden. null ist das EINZIGE Endsignal. Auch auf eine Seite mit weniger als page_size Einträgen können weitere folgen.

page_sizeinteger
kann null sein

MAXIMALE Datensatzanzahl, die der Server für diese Seite verwendet hat, bereits begrenzt. Nicht die tatsächliche Anzahl in items.

totalinteger · int64
kann null sein

Anzahl der zum Filter passenden Zeilen. null, wenn das Zählen zu aufwendig ist. Nicht zur Erkennung des Endes verwenden; dafür dient next_cursor.

warningsobject[]

Erklärung der Liste; leer ohne Besonderheiten. Geschlossene Codes: filtered_by_key_package_filters, filtered_by_provider_scope, no_price_for_org, price_filter_removed_rows, provider_disabled, capability_missing. Sie stehen für Schlüsselregeln, Anbieterumfang, fehlende Preise, Preisfilter, deaktivierte Anbieter oder fehlende Fähigkeiten. count ist die betroffene Zeilenzahl, sonst null.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
kann null sein
—
messagestring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

Bestellung erstellen (Direktkauf)

scope orders:writeIdempotency-Key erforderlichpost_orders

Erforderlicher Berechtigungsbereich: orders:write.

Header (1)

Idempotency-Keystringerforderlich

Vom Client erzeugte Zeichenfolge: Gleicher Schlüssel und gleicher Body werden einmal ausgeführt; Wiederholungen liefern das erste Ergebnis. Gilt je api_key_id (§11): zwei Schlüssel sind getrennte Befehle.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
external_refstring
kann null sein

Ihre eigene Referenz, unverändert in Antwort und Webhooks zurückgegeben.

includestring[]
kann null sein

Zusatzblöcke: ["lpa"] oder ["lpa","qr"]. Ohne Angabe bleibt items[].install null und jede eSIM stornierbar. DIE AUSGABE DES CODES IST SEINE OFFENLEGUNG: lpa schließt das Stornierungsfenster bereits bei Bestellung.

invoice_buyerobject
kann null sein

Nur Mitgliederschlüssel. Ohne Angaben gilt der Verbraucherablauf ohne Organisation oder vietnamesische juristische Person. Angaben folgen der Personenprüfung: full_name, persönliche 12-stellige tax_code und Checkout-Regeln für address/email. Geschäftsrechnungen werden nach dem Kauf beantragt. Im Testmodus wird nur der Snapshot geprüft, keine MISA-Rechnung ausgestellt.

addressstring
kann null sein
—
emailstring
kann null sein
—
full_namestring
kann null sein
—
legal_namestring
kann null sein
—
tax_codestring
kann null sein
—
itemsobject[]
erforderlich

Zu kaufende Pakete und Mengen (höchstens 50 eSIMs).

package_codestring
erforderlich

Paketcode aus IHREM Katalog, also public_sku. Pro Paket UNVERÄNDERLICH, auch bei Anbieterwechsel, daher als Abgleichschlüssel geeignet. Der Beispielwert hat das richtige Format, funktioniert aber NICHT; echten Code aus GET /v1/catalog/packages abrufen.

quantityinteger
erforderlich

Anzahl der für dieses Paket zu kaufenden eSIMs (≥ 1).

Antwort

200Gespeichertes Bestellergebnis wiedergegeben

Wiederholung innerhalb von 24 Stunden mit gleichem Idempotency-Key und Body: IDENTISCHER Body, keine zweite Abbuchung, Header Idempotent-Replayed: true. Eine neue Ausführung liefert 201; erkennen Sie Erfolg nicht nur an diesem Status.

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich

Partnerbestellung.

created_atstring · date-time
—
external_refstring
kann null sein
—
fulfillment_statusstring

Tatsächlich gelieferter Umfang, GETRENNT von status. Bei 3 eSIMs, davon 2 geliefert und 1 fehlgeschlagen, bleibt status=completed, mit fulfillment_status=partially_fulfilled und refunded_amount > 0.

fulfilledpartially_fulfilledunfulfilled
idstring · uuid

Bestell-ID (UUID).

idempotent_replayboolean
kann null sein

Wahr, wenn diese Anfrage einen früheren Erfolg idempotent wiederholt.

invoice_nostring
kann null sein

Rechnungsnummer nach Ausstellung.

invoice_statusstring

Status der Mehrwertsteuerrechnung zur Bestellung. none = noch nicht ausgestellt.

nonependingissuingissuedfailedcancelled
itemsobject[]
—
cancellableobject

Stornierbarkeit der eSIM und Ablehnungsgrund; vor POST /v1/esims/{order_item_id}/cancel prüfen. Dieser Block ist immer vorhanden, nie null: Er ist die Antwort.

allowedboolean
—
disclosed_atstring · date-time
kann null sein

Zeitpunkt, zu dem der Code unser System verlassen hat (ISO-8601 UTC); null, solange niemand ihn gesehen hat. Nachweis bei strittiger Offenlegung und verweigerter Stornierung.

reasonstring
kann null sein

Stornierungsgrund: code_disclosed (Code versendet oder angezeigt), wrong_status (falscher Zustand), expired (Tarif abgelaufen), not_supported (Funktion nicht angegeben). null bei allowed = true. Wie meta.reason bei esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
kann null sein

Ihre Bestellreferenz (external_ref beim Kauf), zur Zuordnung der eSIM zu Ihrer Bestellung. null, wenn nicht angegeben.

iccidstring
kann null sein
—
idstring · uuid

eSIM-ID für alle /v1/esims/{id}/…-Pfade, zugleich order_item_id in Webhooks. Liegt nur die ICCID vor, GET /v1/esims?iccid=… aufrufen, um die id zu erhalten.

installobject
kann null sein

Alle zur INSTALLATION dieser eSIM benötigten Daten. null bedeutet NICHT ANGEFORDERT (ohne include=lpa). Hat die eSIM noch keinen Code, ist der Block dagegen mit lpa: null vorhanden.

Eine Antwort mit diesem Block hat immer Cache-Control: no-store.

android_urlstring
kann null sein
—
apnstring
kann null sein

Zugangspunktname (APN), falls die automatische Einrichtung nicht erfolgt. In dieser Version IMMER null: unbekannt, nicht unnötig.

ios_urlstring
kann null sein
—
lpastring
kann null sein

Aktivierungscode der eSIM, ein verkäufliches Geheimnis. Diese Zeichenfolge genügt zum Erzeugen des QR-Codes.

matching_idstring
kann null sein
—
qr_code_base64string
kann null sein

QR-Code als PNG-Daten-URI. Nur mit include=qr auf einem DETAIL-Endpunkt vorhanden, NIE in Listen (ca. 11 KB pro Zeile, also ca. 1,1 MB bei 100 Zeilen). QR-Code stattdessen aus lpa erzeugen.

smdp_addressstring
kann null sein
—
msisdnstring
kann null sein
—
order_idstring · uuid
—
package_codestring
kann null sein

Paket-CODE aus dem Katalog als Zuordnungsschlüssel für diese eSIM. Nur null, wenn das Ursprungspaket aus dem Katalog entfernt wurde.

package_namestring

Lesbarer Anzeigename, der sich ändern kann. NICHT als Abgleichschlüssel verwenden.

planobject
kann null sein

Der aktuell für diese eSIM geltende Tarif.

current_codestring
—
expires_atstring · date-time
kann null sein
—
started_atstring · date-time
kann null sein
—
smdp_statusstring
kann null sein
—
statusstring
—
updated_atstring · date-time
—
usageobject
kann null sein

Datenverbrauchsstatistik der eSIM. updated_at = null, wenn noch nie abgefragt.

expires_atstring · date-time
kann null sein
—
remaining_bytesinteger · int64
kann null sein
—
sourcestring
kann null sein
—
pollwebhook
total_bytesinteger · int64
kann null sein
—
updated_atstring · date-time
kann null sein
—
used_bytesinteger · int64
kann null sein
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
money_settledboolean

Ob die Bestellung vollständig abgerechnet ist, ohne laufende Erstattung. Bei false kann keine Rechnung ausgestellt werden.

netobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
public_refstring
kann null sein

Kurzer lesbarer Bestellcode für Supportanfragen.

refundedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
statusstring
—
completedpending_providerprovider_failedcancelled
totalobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
okboolean
erforderlich
—
true
201Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich

Partnerbestellung.

created_atstring · date-time
—
external_refstring
kann null sein
—
fulfillment_statusstring

Tatsächlich gelieferter Umfang, GETRENNT von status. Bei 3 eSIMs, davon 2 geliefert und 1 fehlgeschlagen, bleibt status=completed, mit fulfillment_status=partially_fulfilled und refunded_amount > 0.

fulfilledpartially_fulfilledunfulfilled
idstring · uuid

Bestell-ID (UUID).

idempotent_replayboolean
kann null sein

Wahr, wenn diese Anfrage einen früheren Erfolg idempotent wiederholt.

invoice_nostring
kann null sein

Rechnungsnummer nach Ausstellung.

invoice_statusstring

Status der Mehrwertsteuerrechnung zur Bestellung. none = noch nicht ausgestellt.

nonependingissuingissuedfailedcancelled
itemsobject[]
—
cancellableobject

Stornierbarkeit der eSIM und Ablehnungsgrund; vor POST /v1/esims/{order_item_id}/cancel prüfen. Dieser Block ist immer vorhanden, nie null: Er ist die Antwort.

allowedboolean
—
disclosed_atstring · date-time
kann null sein

Zeitpunkt, zu dem der Code unser System verlassen hat (ISO-8601 UTC); null, solange niemand ihn gesehen hat. Nachweis bei strittiger Offenlegung und verweigerter Stornierung.

reasonstring
kann null sein

Stornierungsgrund: code_disclosed (Code versendet oder angezeigt), wrong_status (falscher Zustand), expired (Tarif abgelaufen), not_supported (Funktion nicht angegeben). null bei allowed = true. Wie meta.reason bei esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
kann null sein

Ihre Bestellreferenz (external_ref beim Kauf), zur Zuordnung der eSIM zu Ihrer Bestellung. null, wenn nicht angegeben.

iccidstring
kann null sein
—
idstring · uuid

eSIM-ID für alle /v1/esims/{id}/…-Pfade, zugleich order_item_id in Webhooks. Liegt nur die ICCID vor, GET /v1/esims?iccid=… aufrufen, um die id zu erhalten.

installobject
kann null sein

Alle zur INSTALLATION dieser eSIM benötigten Daten. null bedeutet NICHT ANGEFORDERT (ohne include=lpa). Hat die eSIM noch keinen Code, ist der Block dagegen mit lpa: null vorhanden.

Eine Antwort mit diesem Block hat immer Cache-Control: no-store.

android_urlstring
kann null sein
—
apnstring
kann null sein

Zugangspunktname (APN), falls die automatische Einrichtung nicht erfolgt. In dieser Version IMMER null: unbekannt, nicht unnötig.

ios_urlstring
kann null sein
—
lpastring
kann null sein

Aktivierungscode der eSIM, ein verkäufliches Geheimnis. Diese Zeichenfolge genügt zum Erzeugen des QR-Codes.

matching_idstring
kann null sein
—
qr_code_base64string
kann null sein

QR-Code als PNG-Daten-URI. Nur mit include=qr auf einem DETAIL-Endpunkt vorhanden, NIE in Listen (ca. 11 KB pro Zeile, also ca. 1,1 MB bei 100 Zeilen). QR-Code stattdessen aus lpa erzeugen.

smdp_addressstring
kann null sein
—
msisdnstring
kann null sein
—
order_idstring · uuid
—
package_codestring
kann null sein

Paket-CODE aus dem Katalog als Zuordnungsschlüssel für diese eSIM. Nur null, wenn das Ursprungspaket aus dem Katalog entfernt wurde.

package_namestring

Lesbarer Anzeigename, der sich ändern kann. NICHT als Abgleichschlüssel verwenden.

planobject
kann null sein

Der aktuell für diese eSIM geltende Tarif.

current_codestring
—
expires_atstring · date-time
kann null sein
—
started_atstring · date-time
kann null sein
—
smdp_statusstring
kann null sein
—
statusstring
—
updated_atstring · date-time
—
usageobject
kann null sein

Datenverbrauchsstatistik der eSIM. updated_at = null, wenn noch nie abgefragt.

expires_atstring · date-time
kann null sein
—
remaining_bytesinteger · int64
kann null sein
—
sourcestring
kann null sein
—
pollwebhook
total_bytesinteger · int64
kann null sein
—
updated_atstring · date-time
kann null sein
—
used_bytesinteger · int64
kann null sein
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
money_settledboolean

Ob die Bestellung vollständig abgerechnet ist, ohne laufende Erstattung. Bei false kann keine Rechnung ausgestellt werden.

netobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
public_refstring
kann null sein

Kurzer lesbarer Bestellcode für Supportanfragen.

refundedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
statusstring
—
completedpending_providerprovider_failedcancelled
totalobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

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
}

Beispiel-Anfragetext

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}

Bestelldetails und eSIMs

scope orders:readget_orders__id_

Erforderlicher Berechtigungsbereich: orders:read.

Pfadparameter (1)

idstringerforderlich

Bestell-ID (UUID).

Query-Parameter (1)

includestringoptional

Kommagetrennte Zusatzblöcke: lpa liefert install, qr zusätzlich install.qr_code_base64 (nur Details). DIE CODEAUSGABE IST OFFENLEGUNG und beendet die Stornierbarkeit jeder betroffenen eSIM dauerhaft (cancellable.allowed = false, reason = code_disclosed). Fordern Sie den Code erst zur Lieferung an. Unbekannter Wert: internal.invalid_filter (400, field: "include").

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich

Partnerbestellung.

created_atstring · date-time
—
external_refstring
kann null sein
—
fulfillment_statusstring

Tatsächlich gelieferter Umfang, GETRENNT von status. Bei 3 eSIMs, davon 2 geliefert und 1 fehlgeschlagen, bleibt status=completed, mit fulfillment_status=partially_fulfilled und refunded_amount > 0.

fulfilledpartially_fulfilledunfulfilled
idstring · uuid

Bestell-ID (UUID).

idempotent_replayboolean
kann null sein

Wahr, wenn diese Anfrage einen früheren Erfolg idempotent wiederholt.

invoice_nostring
kann null sein

Rechnungsnummer nach Ausstellung.

invoice_statusstring

Status der Mehrwertsteuerrechnung zur Bestellung. none = noch nicht ausgestellt.

nonependingissuingissuedfailedcancelled
itemsobject[]
—
cancellableobject

Stornierbarkeit der eSIM und Ablehnungsgrund; vor POST /v1/esims/{order_item_id}/cancel prüfen. Dieser Block ist immer vorhanden, nie null: Er ist die Antwort.

allowedboolean
—
disclosed_atstring · date-time
kann null sein

Zeitpunkt, zu dem der Code unser System verlassen hat (ISO-8601 UTC); null, solange niemand ihn gesehen hat. Nachweis bei strittiger Offenlegung und verweigerter Stornierung.

reasonstring
kann null sein

Stornierungsgrund: code_disclosed (Code versendet oder angezeigt), wrong_status (falscher Zustand), expired (Tarif abgelaufen), not_supported (Funktion nicht angegeben). null bei allowed = true. Wie meta.reason bei esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
kann null sein

Ihre Bestellreferenz (external_ref beim Kauf), zur Zuordnung der eSIM zu Ihrer Bestellung. null, wenn nicht angegeben.

iccidstring
kann null sein
—
idstring · uuid

eSIM-ID für alle /v1/esims/{id}/…-Pfade, zugleich order_item_id in Webhooks. Liegt nur die ICCID vor, GET /v1/esims?iccid=… aufrufen, um die id zu erhalten.

installobject
kann null sein

Alle zur INSTALLATION dieser eSIM benötigten Daten. null bedeutet NICHT ANGEFORDERT (ohne include=lpa). Hat die eSIM noch keinen Code, ist der Block dagegen mit lpa: null vorhanden.

Eine Antwort mit diesem Block hat immer Cache-Control: no-store.

android_urlstring
kann null sein
—
apnstring
kann null sein

Zugangspunktname (APN), falls die automatische Einrichtung nicht erfolgt. In dieser Version IMMER null: unbekannt, nicht unnötig.

ios_urlstring
kann null sein
—
lpastring
kann null sein

Aktivierungscode der eSIM, ein verkäufliches Geheimnis. Diese Zeichenfolge genügt zum Erzeugen des QR-Codes.

matching_idstring
kann null sein
—
qr_code_base64string
kann null sein

QR-Code als PNG-Daten-URI. Nur mit include=qr auf einem DETAIL-Endpunkt vorhanden, NIE in Listen (ca. 11 KB pro Zeile, also ca. 1,1 MB bei 100 Zeilen). QR-Code stattdessen aus lpa erzeugen.

smdp_addressstring
kann null sein
—
msisdnstring
kann null sein
—
order_idstring · uuid
—
package_codestring
kann null sein

Paket-CODE aus dem Katalog als Zuordnungsschlüssel für diese eSIM. Nur null, wenn das Ursprungspaket aus dem Katalog entfernt wurde.

package_namestring

Lesbarer Anzeigename, der sich ändern kann. NICHT als Abgleichschlüssel verwenden.

planobject
kann null sein

Der aktuell für diese eSIM geltende Tarif.

current_codestring
—
expires_atstring · date-time
kann null sein
—
started_atstring · date-time
kann null sein
—
smdp_statusstring
kann null sein
—
statusstring
—
updated_atstring · date-time
—
usageobject
kann null sein

Datenverbrauchsstatistik der eSIM. updated_at = null, wenn noch nie abgefragt.

expires_atstring · date-time
kann null sein
—
remaining_bytesinteger · int64
kann null sein
—
sourcestring
kann null sein
—
pollwebhook
total_bytesinteger · int64
kann null sein
—
updated_atstring · date-time
kann null sein
—
used_bytesinteger · int64
kann null sein
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
money_settledboolean

Ob die Bestellung vollständig abgerechnet ist, ohne laufende Erstattung. Bei false kann keine Rechnung ausgestellt werden.

netobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
public_refstring
kann null sein

Kurzer lesbarer Bestellcode für Supportanfragen.

refundedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
statusstring
—
completedpending_providerprovider_failedcancelled
totalobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

E-Mail bzw. QR-Code der Bestellung erneut senden

scope orders:writepost_orders__id__resend

Erforderlicher Berechtigungsbereich: orders:write.

Pfadparameter (1)

idstringerforderlich

Bestell-ID (UUID).

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
idstring · uuid

ID der soeben erneut gesendeten Bestellung.

modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
resent_atstring · date-time
—
resent_tostring

Teilweise maskierte Empfängeradresse; die Partneroberfläche gibt die vollständige Kundenadresse nie erneut aus.

okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

Mehrzeiligen Auftrag ausführen (eine Bestellung pro Zeile)

scope orders:writeIdempotency-Key erforderlichpost_orders_bulk

Erforderlicher Berechtigungsbereich: orders:write.

Mehrzeilige Bestellung: Jede Zeile erzeugt eine unabhängige Bestellung mit quote→commit wie POST /v1/orders.

TEILERFOLG: Fehler einer Zeile blockieren andere nicht; nur erfolgreiche Zeilen werden belastet. Fachliche Fehler (ausverkauft, Guthabenmangel, nicht kaufbares Paket, Missbrauchsgrenze) stehen in lines[].error, ohne den HTTP-Status des Auftrags zu ändern. Nach Guthabenerschöpfung werden weitere Zeilen trotzdem versucht, da günstigere noch bezahlbar sein können.

Status immer 200: gemischte Zeilenergebnisse statt einer neu angelegten Einzelressource (201); vollständige Verarbeitung innerhalb der Anfrage, kein 202 mit Polling.

GLEICHER Idempotency-Key und GLEICHER Body laden den bestehenden Auftrag, liefern fertige Ergebnisse und verarbeiten nur pending-Zeilen weiter, ohne Doppelbelastung. Wurde keine Zeile ausgeführt, gilt Idempotent-Replayed: true. Gleicher Schlüssel mit anderem Body: idempotency.conflict.

Zwei inhaltlich identische Aufträge derselben Organisation innerhalb von 10 Minuten gelten auch mit unterschiedlichen Schlüsseln als versehentliches Duplikat: alter Auftrag mit Idempotent-Replayed: true, keine erneute Belastung. Für einen echten identischen Neukauf external_ref ändern. Es gehört zum signierten Inhalt und unterscheidet die Aufträge auch für den Abgleich. Vollständig fehlgeschlagene Aufträge (keine Bestellung erstellt) werden nie gesperrt; nach 10 Minuten ist eine neue identische Bestellung wieder möglich.

Zeilenreferenzen erscheinen in lines[].external_ref, die Auftragsreferenz oben in der Antwort.

Kein LPA/QR in der Antwort; je Bestellung GET /v1/orders/{order_id} abrufen.

Header (1)

Idempotency-Keystringerforderlich

Vom Client erzeugte Zeichenfolge: Gleicher Schlüssel und gleicher Body werden einmal ausgeführt; Wiederholungen liefern das erste Ergebnis. Gilt je api_key_id (§11): zwei Schlüssel sind getrennte Befehle.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
external_refstring
kann null sein

Referenz für den GESAMTEN Auftrag, in der Auftragsantwort zurückgegeben.

itemsobject[]
erforderlich

Jeder Eintrag erstellt eine unabhängige Bestellung. Konfigurierbares Zeilenlimit (Standard 100); Überschreitung: internal.limit_exceeded (422).

external_refstring
kann null sein

Referenz nur für DIESE Zeile, unverändert im Zeilenergebnis zurückgegeben.

package_codestring
erforderlich

Paketcode aus IHREM Katalog, also public_sku. Pro Paket UNVERÄNDERLICH, auch bei Anbieterwechsel, daher als Abgleichschlüssel geeignet. Der Beispielwert hat das richtige Format, funktioniert aber NICHT; echten Code aus GET /v1/catalog/packages abrufen.

quantityinteger
erforderlich
—

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich

Ergebnis einer Sammelbestellung. TEILERFOLG: Fehlerhafte Zeilen blockieren andere nicht; nur done-Zeilen werden belastet. Kein LPA/QR; je order_id über GET /v1/orders/{id} abrufen.

created_atstring · date-time
—
external_refstring
kann null sein
—
failed_linesinteger
—
idstring · uuid
—
itemsobject[]
—
chargedobject
kann null sein
—
errorobject
kann null sein

Echter Fehlercode domain.reason, z. B. wallet.insufficient_balance, esim.out_of_stock, order.package_not_allowed, wallet.velocity_exceeded.

codestring
—
messagestring
—
external_refstring
kann null sein
—
line_nointeger

Ab 1, genau in der eingereichten Reihenfolge.

order_idstring · uuid
kann null sein
—
package_codestring
—
quantityinteger
—
statusstring
—
pendingdonefailed
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
statusstring

pending: Nach einer Unterbrechung sind noch Zeilen offen. Rufen Sie mit demselben Idempotency-Key erneut auf, um sie abzuschließen.

pendingcompletedpartialfailed
succeeded_linesinteger
—
total_chargedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
total_linesinteger
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

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
}

Beispiel-Anfragetext

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

Ergebnis einer Sammelbestellung

scope orders:readget_orders_bulk__id_

Erforderlicher Berechtigungsbereich: orders:read.

Ergebnisse je Zeile eines bereits abgesendeten Auftrags erneut lesen, um eine verlorene Antwort ohne erneutes Senden des Bodys wiederherzustellen. Aufträge anderer Organisationen oder Umgebungen (Live/Test) führen zu 404.

Pfadparameter (1)

idstringerforderlich

Von POST /v1/orders/bulk zurückgegebene Auftrags-ID (UUID).

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich

Ergebnis einer Sammelbestellung. TEILERFOLG: Fehlerhafte Zeilen blockieren andere nicht; nur done-Zeilen werden belastet. Kein LPA/QR; je order_id über GET /v1/orders/{id} abrufen.

created_atstring · date-time
—
external_refstring
kann null sein
—
failed_linesinteger
—
idstring · uuid
—
itemsobject[]
—
chargedobject
kann null sein
—
errorobject
kann null sein

Echter Fehlercode domain.reason, z. B. wallet.insufficient_balance, esim.out_of_stock, order.package_not_allowed, wallet.velocity_exceeded.

codestring
—
messagestring
—
external_refstring
kann null sein
—
line_nointeger

Ab 1, genau in der eingereichten Reihenfolge.

order_idstring · uuid
kann null sein
—
package_codestring
—
quantityinteger
—
statusstring
—
pendingdonefailed
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
statusstring

pending: Nach einer Unterbrechung sind noch Zeilen offen. Rufen Sie mit demselben Idempotency-Key erneut auf, um sie abzuschließen.

pendingcompletedpartialfailed
succeeded_linesinteger
—
total_chargedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
total_linesinteger
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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-Datensätze, Verbrauch, Aufladung und Stornierung

GET/v1/esims

eSIMs auflisten (abfragebasierter Abgleich)

scope esims:readget_esims

Erforderlicher Berechtigungsbereich: esims:read.

Query-Parameter (8)

page_sizeintegeroptionalStandard 50min 1max 200

Datensätze pro Seite. Standard 50, höchstens 200. Höhere Werte werden ohne Fehler auf 200 begrenzt.

statusstringoptional
Erlaubte Werte:pendingavailablereservedsolddeliveredin_useused_upexpiredsuspendedcancelledprovider_failed

Nach esim_status filtern. Unbekannte Werte führen zu internal.invalid_filter (400), nicht zu einer leeren Liste. Ein Tippfehler wie canceled statt cancelled darf nicht als fehlende eSIM missverstanden werden.

order_idstring · uuidoptional

Nur eSIMs einer Bestellung.

iccidstringoptional

Nach ICCID filtern (Zeichenfolge, exakte Übereinstimmung).

external_refstringoptional

Alle eSIMs der Bestellungen mit Ihrer Referenz external_ref (exakte Übereinstimmung).

updated_sincestring · date-timeoptional

ISO-8601 UTC. Liefert alle Datensätze mit updated_at >= updated_since in aufsteigender Reihenfolge. Dies ist der Standardweg für ABFRAGEBASIERTEN ABGLEICH (§8.5), auch bei verpassten Webhooks. Falsches Format: internal.invalid_filter (400).

cursorstringoptional

Nächster Cursor: Senden Sie next_cursor unverändert zurück. Ein ungültiger Cursor beginnt ohne Fehler am Listenanfang, damit Änderungen der Codierung Integrationen nicht brechen. Prüfen Sie, ob der Cursor wechselt, um Endlosschleifen auf Seite eins zu vermeiden.

includestringoptional

Kommagetrennte Zusatzblöcke: lpa liefert install, qr zusätzlich install.qr_code_base64 (nur Details). DIE CODEAUSGABE IST OFFENLEGUNG und beendet die Stornierbarkeit jeder betroffenen eSIM dauerhaft (cancellable.allowed = false, reason = code_disclosed). Fordern Sie den Code erst zur Lieferung an. Unbekannter Wert: internal.invalid_filter (400, field: "include").

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
itemsobject[]
—
cancellableobject

Stornierbarkeit der eSIM und Ablehnungsgrund; vor POST /v1/esims/{order_item_id}/cancel prüfen. Dieser Block ist immer vorhanden, nie null: Er ist die Antwort.

allowedboolean
—
disclosed_atstring · date-time
kann null sein

Zeitpunkt, zu dem der Code unser System verlassen hat (ISO-8601 UTC); null, solange niemand ihn gesehen hat. Nachweis bei strittiger Offenlegung und verweigerter Stornierung.

reasonstring
kann null sein

Stornierungsgrund: code_disclosed (Code versendet oder angezeigt), wrong_status (falscher Zustand), expired (Tarif abgelaufen), not_supported (Funktion nicht angegeben). null bei allowed = true. Wie meta.reason bei esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
kann null sein

Ihre Bestellreferenz (external_ref beim Kauf), zur Zuordnung der eSIM zu Ihrer Bestellung. null, wenn nicht angegeben.

iccidstring
kann null sein
—
idstring · uuid

eSIM-ID für alle /v1/esims/{id}/…-Pfade, zugleich order_item_id in Webhooks. Liegt nur die ICCID vor, GET /v1/esims?iccid=… aufrufen, um die id zu erhalten.

installobject
kann null sein

Alle zur INSTALLATION dieser eSIM benötigten Daten. null bedeutet NICHT ANGEFORDERT (ohne include=lpa). Hat die eSIM noch keinen Code, ist der Block dagegen mit lpa: null vorhanden.

Eine Antwort mit diesem Block hat immer Cache-Control: no-store.

android_urlstring
kann null sein
—
apnstring
kann null sein

Zugangspunktname (APN), falls die automatische Einrichtung nicht erfolgt. In dieser Version IMMER null: unbekannt, nicht unnötig.

ios_urlstring
kann null sein
—
lpastring
kann null sein

Aktivierungscode der eSIM, ein verkäufliches Geheimnis. Diese Zeichenfolge genügt zum Erzeugen des QR-Codes.

matching_idstring
kann null sein
—
qr_code_base64string
kann null sein

QR-Code als PNG-Daten-URI. Nur mit include=qr auf einem DETAIL-Endpunkt vorhanden, NIE in Listen (ca. 11 KB pro Zeile, also ca. 1,1 MB bei 100 Zeilen). QR-Code stattdessen aus lpa erzeugen.

smdp_addressstring
kann null sein
—
msisdnstring
kann null sein
—
order_idstring · uuid
—
package_codestring
kann null sein

Paket-CODE aus dem Katalog als Zuordnungsschlüssel für diese eSIM. Nur null, wenn das Ursprungspaket aus dem Katalog entfernt wurde.

package_namestring

Lesbarer Anzeigename, der sich ändern kann. NICHT als Abgleichschlüssel verwenden.

planobject
kann null sein

Der aktuell für diese eSIM geltende Tarif.

current_codestring
—
expires_atstring · date-time
kann null sein
—
started_atstring · date-time
kann null sein
—
smdp_statusstring
kann null sein
—
statusstring
—
updated_atstring · date-time
—
usageobject
kann null sein

Datenverbrauchsstatistik der eSIM. updated_at = null, wenn noch nie abgefragt.

expires_atstring · date-time
kann null sein
—
remaining_bytesinteger · int64
kann null sein
—
sourcestring
kann null sein
—
pollwebhook
total_bytesinteger · int64
kann null sein
—
updated_atstring · date-time
kann null sein
—
used_bytesinteger · int64
kann null sein
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_cursorstring
kann null sein

Für die nächste Seite UNVERÄNDERT zurücksenden. null ist das EINZIGE Endsignal. Auch auf eine Seite mit weniger als page_size Einträgen können weitere folgen.

page_sizeinteger
kann null sein

MAXIMALE Datensatzanzahl, die der Server für diese Seite verwendet hat, bereits begrenzt. Nicht die tatsächliche Anzahl in items.

totalinteger · int64
kann null sein

Anzahl der zum Filter passenden Zeilen. null, wenn das Zählen zu aufwendig ist. Nicht zur Erkennung des Endes verwenden; dafür dient next_cursor.

warningsobject[]

Erklärung der Liste; leer ohne Besonderheiten. Geschlossene Codes: filtered_by_key_package_filters, filtered_by_provider_scope, no_price_for_org, price_filter_removed_rows, provider_disabled, capability_missing. Sie stehen für Schlüsselregeln, Anbieterumfang, fehlende Preise, Preisfilter, deaktivierte Anbieter oder fehlende Fähigkeiten. count ist die betroffene Zeilenzahl, sonst null.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
kann null sein
—
messagestring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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}

Eine eSIM abrufen

scope esims:readget_esims__id_

Erforderlicher Berechtigungsbereich: esims:read.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Query-Parameter (1)

includestringoptional

Kommagetrennte Zusatzblöcke: lpa liefert install, qr zusätzlich install.qr_code_base64 (nur Details). DIE CODEAUSGABE IST OFFENLEGUNG und beendet die Stornierbarkeit jeder betroffenen eSIM dauerhaft (cancellable.allowed = false, reason = code_disclosed). Fordern Sie den Code erst zur Lieferung an. Unbekannter Wert: internal.invalid_filter (400, field: "include").

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
cancellableobject

Stornierbarkeit der eSIM und Ablehnungsgrund; vor POST /v1/esims/{order_item_id}/cancel prüfen. Dieser Block ist immer vorhanden, nie null: Er ist die Antwort.

allowedboolean
—
disclosed_atstring · date-time
kann null sein

Zeitpunkt, zu dem der Code unser System verlassen hat (ISO-8601 UTC); null, solange niemand ihn gesehen hat. Nachweis bei strittiger Offenlegung und verweigerter Stornierung.

reasonstring
kann null sein

Stornierungsgrund: code_disclosed (Code versendet oder angezeigt), wrong_status (falscher Zustand), expired (Tarif abgelaufen), not_supported (Funktion nicht angegeben). null bei allowed = true. Wie meta.reason bei esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
kann null sein

Ihre Bestellreferenz (external_ref beim Kauf), zur Zuordnung der eSIM zu Ihrer Bestellung. null, wenn nicht angegeben.

iccidstring
kann null sein
—
idstring · uuid

eSIM-ID für alle /v1/esims/{id}/…-Pfade, zugleich order_item_id in Webhooks. Liegt nur die ICCID vor, GET /v1/esims?iccid=… aufrufen, um die id zu erhalten.

installobject
kann null sein

Alle zur INSTALLATION dieser eSIM benötigten Daten. null bedeutet NICHT ANGEFORDERT (ohne include=lpa). Hat die eSIM noch keinen Code, ist der Block dagegen mit lpa: null vorhanden.

Eine Antwort mit diesem Block hat immer Cache-Control: no-store.

android_urlstring
kann null sein
—
apnstring
kann null sein

Zugangspunktname (APN), falls die automatische Einrichtung nicht erfolgt. In dieser Version IMMER null: unbekannt, nicht unnötig.

ios_urlstring
kann null sein
—
lpastring
kann null sein

Aktivierungscode der eSIM, ein verkäufliches Geheimnis. Diese Zeichenfolge genügt zum Erzeugen des QR-Codes.

matching_idstring
kann null sein
—
qr_code_base64string
kann null sein

QR-Code als PNG-Daten-URI. Nur mit include=qr auf einem DETAIL-Endpunkt vorhanden, NIE in Listen (ca. 11 KB pro Zeile, also ca. 1,1 MB bei 100 Zeilen). QR-Code stattdessen aus lpa erzeugen.

smdp_addressstring
kann null sein
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
msisdnstring
kann null sein
—
order_idstring · uuid
—
package_codestring
kann null sein

Paket-CODE aus dem Katalog als Zuordnungsschlüssel für diese eSIM. Nur null, wenn das Ursprungspaket aus dem Katalog entfernt wurde.

package_namestring

Lesbarer Anzeigename, der sich ändern kann. NICHT als Abgleichschlüssel verwenden.

planobject
kann null sein

Der aktuell für diese eSIM geltende Tarif.

current_codestring
—
expires_atstring · date-time
kann null sein
—
started_atstring · date-time
kann null sein
—
smdp_statusstring
kann null sein
—
statusstring
—
updated_atstring · date-time
—
usageobject
kann null sein

Datenverbrauchsstatistik der eSIM. updated_at = null, wenn noch nie abgefragt.

expires_atstring · date-time
kann null sein
—
remaining_bytesinteger · int64
kann null sein
—
sourcestring
kann null sein
—
pollwebhook
total_bytesinteger · int64
kann null sein
—
updated_atstring · date-time
kann null sein
—
used_bytesinteger · int64
kann null sein
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

Unbenutzte eSIM stornieren

scope esims:writeIdempotency-Key erforderlichpost_esims__id__cancel

Erforderlicher Berechtigungsbereich: esims:write.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Header (1)

Idempotency-Keystringerforderlich

Vom Client erzeugte Zeichenfolge: Gleicher Schlüssel und gleicher Body werden einmal ausgeführt; Wiederholungen liefern das erste Ergebnis. Gilt je api_key_id (§11): zwei Schlüssel sind getrennte Befehle.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
cancelled_atstring · date-time
—
esim_idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
refund_suggestedboolean

true: Erstattung wartet auf manuelle Freigabe; es wurde noch kein Geld bewegt. refunded.amount = 0 bedeutet nicht, dass keine Erstattung vorgesehen ist.

refundedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
statusstring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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)"

Beispielantwort

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 aufladen

scope esims:writeIdempotency-Key erforderlichpost_esims__id__change-plan

Erforderlicher Berechtigungsbereich: esims:write.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Header (1)

Idempotency-Keystringerforderlich

Vom Client erzeugte Zeichenfolge: Gleicher Schlüssel und gleicher Body werden einmal ausgeführt; Wiederholungen liefern das erste Ergebnis. Gilt je api_key_id (§11): zwei Schlüssel sind getrennte Befehle.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
package_codestring
erforderlich

Paketcode aus IHREM Katalog, also public_sku. Pro Paket UNVERÄNDERLICH, auch bei Anbieterwechsel, daher als Abgleichschlüssel geeignet. Der Beispielwert hat das richtige Format, funktioniert aber NICHT; echten Code aus GET /v1/catalog/packages abrufen.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich

Ergebnis des eSIM-Paketwechsels.

chargedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
current_planobject
—
codestring
—
expires_atstring · date-time
kann null sein
—
started_atstring · date-time
kann null sein
—
esim_idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
order_idstring · uuid
—
previous_planobject
kann null sein
—
cancelled_atstring · date-time
kann null sein
—
codestring
—
remaining_bytes_forfeitedinteger · int64
kann null sein
—
warningstring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

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
}

Beispiel-Anfragetext

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

Gültige Aufladepakete für diese eSIM

scope esims:readget_esims__id__plan-options

Erforderlicher Berechtigungsbereich: esims:read.

Liefert nur gültige Aufladepakete für GENAU DIESE eSIM. Der Anbieter wird aus der id ermittelt und entsprechend gefiltert. /v1 zeigt nirgends provider_id; der Server verhindert damit strukturell die Wahl eines Pakets eines anderen Anbieters. Ein nicht gelisteter Code bei POST /v1/esims/{id}/change-plan führt zu 409 esim.topup_provider_mismatch.

Ohne zugeordneten Anbieter (reiner Import oder fehlende Quelldaten) wird korrekt eine leere Liste (total: 0) zurückgegeben, kein Fehler.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Query-Parameter (2)

page_sizeintegeroptionalStandard 50min 1max 200

Datensätze pro Seite. Standard 50, höchstens 200. Höhere Werte werden ohne Fehler auf 200 begrenzt.

cursorstringoptional

Nächster Cursor: Senden Sie next_cursor unverändert zurück. Ein ungültiger Cursor beginnt ohne Fehler am Listenanfang, damit Änderungen der Codierung Integrationen nicht brechen. Prüfen Sie, ob der Cursor wechselt, um Endlosschleifen auf Seite eins zu vermeiden.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich

Für diese eSIM verfügbare Aufladepakete, nach ihrem Anbieter gefiltert. Jeder Eintrag ist ein vollständiges Package wie im Katalog. policy beschreibt den aktuellen Anbieter; null heißt unbekannte Richtlinie, nicht „keine Stornierung“.

itemsobject[]
—
capabilitiesobject

Nachverkaufsfunktionen aus der tatsächlichen Paketdeklaration, nie vom Anbietertyp abgeleitet. Ohne Angaben sind alle Werte false, um keine Aktion zu versprechen, die sicher 422 capability.not_supported ergibt.

cancelobject
—
restrictionstring
kann null sein

code_not_disclosed: Stornierung nur, wenn niemand den Code gesehen hat (weder Kunde noch Dashboard noch ?include=lpa). Das ist die echte Bedingung, unabhängig von einer Installation, die das System nicht kennt. Die alte Bezeichnung unused_only war irreführend.

code_not_disclosednull
supportedboolean
—
plan_changeobject
—
policyobject
kann null sein

Verhalten des Anbieters beim AUFLADEN EINES NEUEN PAKETS. null bedeutet: nicht aufladbar ODER Anbieterrichtlinie noch nicht ermittelt. null verbietet Annahmen; ein Objekt nur mit false würde dagegen garantieren, dass nichts storniert wird.

……

Tiefer verschachtelte Felder: siehe JSON-Beispiel.

supportedboolean
—
usage_checkobject
—
supportedboolean
—
codestring

Paketcode aus IHREM Katalog, also public_sku. Pro Paket UNVERÄNDERLICH, auch bei Anbieterwechsel, daher als Abgleichschlüssel geeignet. Der Beispielwert hat das richtige Format, funktioniert aber NICHT; echten Code aus GET /v1/catalog/packages abrufen.

coverageobject

Nutzungsgebiete des Pakets.

countriesstring[]

ISO-3166-Alpha-2-Codes. Leer für globale Pakete (keine Aufzählung von 200 Ländern) und Pakete ohne deklarierte Abdeckung.

country_countinteger

Anzahl abgedeckter Länder, um Mehrländerpakete ohne vollständigen Abruf und eigenes Zählen zu sortieren oder zu filtern.

groupstring

Abdeckung: Länder (country), Region (region) oder weltweit (global). Offene Aufzählung: Unbekannte Werte ignorieren.

regionstring
kann null sein

Immer null: Regionen gehören zu Ländern, nicht zu Paketen. Der Filter ?region= funktioniert weiterhin.

dataobject

Datenvolumen des Pakets. Alle Werte in BYTES, nicht MB/GB, um doppelte Rundung zu vermeiden.

daily_bytesinteger · int64
kann null sein

TAGESDATENVOLUMEN in Bytes. null bei fixed- oder unlimited-Paketen.

labelstring

Lesbare Bezeichnung aus derselben Funktion wie auf allen unseren Oberflächen.

modelstring

Berechnungsart: daily (Tagesvolumen), fixed (Gesamtvolumen im Zeitraum), unlimited. OFFENE ENUMERATION: Clients MÜSSEN unbekannte Werte ignorieren.

dailyfixedunlimited
resetobject
kann null sein

Rücksetzzeit des Tagesvolumens, wichtig beim Kauf spät am Tag. null = nicht angegeben.

atstring
kann null sein

Zurücksetzzeit in der lokalen Zeitzone timezone (00:00, 00:00:01).

notestring
kann null sein
—
timezonestring
kann null sein

Zeitzone der Rücksetzung (UTC+8, UTC+7).

total_bytesinteger · int64
kann null sein

Gesamtvolumen des Zeitraums in Bytes. null für daily oder unlimited: Tagesvolumen mal Tage ist keine vom Anbieter zugesicherte Gesamtmenge.

descriptionstring
kann null sein

Verkaufsbeschreibung des Pakets. null, wenn noch nicht verfasst.

description_enstring
kann null sein

Englische Beschreibung; null, wenn nicht angegeben.

featuresobject

Paketfunktionen als typisiertes Objekt statt Codeliste: false und null unterscheiden fehlendes Tethering von unbekannter Information; [] kann das nicht.

appsobject[]

App-Unterstützung nach BETRIEBSSYSTEM.

Eine NICHT im Array vorhandene App bedeutet UNBEKANNT, nicht „funktioniert“. Nur gelistete Einträge sind Anbieterzusagen; derzeit ist nur TikTok angegeben.

Es gibt bewusst kein zusammengefasstes Feld: Im echten Katalog unterstützen 2.465 Pakete TikTok auf dem iPhone, aber NICHT auf Android. Ein einziges Flag würde sie auf „nein“ reduzieren und fast ein Drittel des Katalogs falsch darstellen.

androidstring

supported (Unterstützung erklärt), blocked (Nichtunterstützung erklärt), unknown (Betriebssystem nicht abgedeckt). Offene Aufzählung.

supportedblockedunknown
appstring

App-Code in Kleinbuchstaben (z. B. tiktok).

iosstring

supported (Unterstützung erklärt), blocked (Nichtunterstützung erklärt), unknown (Betriebssystem nicht abgedeckt). Offene Aufzählung.

supportedblockedunknown
notestring
kann null sein
—
hotspotboolean
kann null sein

WLAN-Tethering für andere Geräte. null = unbekannt, nicht false.

kyc_requiredboolean
kann null sein

Vor Nutzung ist eine Identitätsprüfung (KYC) erforderlich. null = nicht angegeben.

smsboolean
kann null sein

SMS. false bedeutet KEIN SMS-Empfang, somit auch keine Bank-OTPs. null = nicht angegeben.

voiceboolean
kann null sein

Sprachanrufe. false bedeutet ausdrücklich keine Telefonie: Eine reine Datenleitung empfängt keine Anrufe. null = unbekannt.

fupobject
kann null sein

Verhalten nach Verbrauch des Hochgeschwindigkeitsvolumens. null = keine Drosselungsangabe.

behaviorstring
kann null sein

throttle (gedrosselt), cutoff (getrennt) oder null (nicht angegeben). Offene Aufzählung.

throttlecutoffnull
high_speed_bytesinteger · int64
kann null sein

Bytes bei voller Geschwindigkeit vor der Drosselung.

notestring
kann null sein
—
speed_kbpsinteger
kann null sein

Geschwindigkeit NACH Verbrauch des schnellen Datenvolumens (kbps). null bei Abschaltung oder fehlender Angabe. Nicht dasselbe wie network.speed_cap_kbps, das Limit des gesamten Pakets.

marketstring

VERSORGUNGSSYSTEM des Pakets, OFFENE Enumeration: unbekannte Werte ignorieren. Bestimmt Bestandsmodell und Nachverkaufsfunktionen. domestic verkauft Kits mit Aufladung, Stornierung und Verbrauchsabfrage; international wird bei Bedarf beim Anbieter bestellt, ohne Bestand.

domesticinternational
namestring
—
name_enstring
kann null sein

Englischer Name; null, wenn nicht angegeben.

networkobject

Netze, die das Paket nutzen kann.

apnstring
kann null sein

APN zur manuellen Eingabe auf bestimmten Android-Geräten ohne automatische Konfiguration. Für vollständige Installationshinweise und weniger Supportfälle erforderlich. null = nicht angegeben.

carriersobject[]

Mobilfunkbetreiber, deren Netze das Paket nutzt. eSIM-Eigenschaft (Funknetz), nicht der Lieferant.

namestring
—
generationsstring[]
kann null sein

Nutzbare Mobilfunkgenerationen (["4g", "5g"]). null = nicht angegeben, NICHT „nur 2G“.

speed_cap_kbpsinteger
kann null sein

Geschwindigkeitslimit für das GESAMTE Paket (kbps), anders als fup.speed_kbps nach Verbrauch des schnellen Volumens.

priceobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
tagsstring[]

Normalisierte kommerzielle Tags (Kleinbuchstaben, Bindestriche), auch Grundlage der Katalogbeschränkungen Ihres Schlüssels. Derzeit trägt kein Paket Tags; ?tag= liefert daher eine leere Liste, bis der Verkäufer Tags vergibt.

validityobject

Gültigkeitsdauer des Pakets und Beginn der Laufzeit.

daysinteger
—
install_by_daysinteger
kann null sein

Tage nach Kauf zum Scannen des QR-Codes bzw. Installieren der eSIM. null = nicht angegeben, NICHT „jederzeit installierbar“.

labelstring
—
reclaim_on_expiryboolean
kann null sein

Der Anbieter zieht das Abonnement bei Ablauf zurück; Aufladen und Tarifwechsel sind danach unmöglich. Wichtig bei Verlängerungen. null = nicht angegeben.

starts_whenstring
kann null sein

Beginn der Laufzeit: on_install (Installation), on_purchase (Kauf) oder null. Entscheidend für das Ablaufdatum: Bei null kein genaues Datum zusagen.

on_installon_purchasenull
unused_valid_daysinteger
kann null sein

Anzahl der Tage, die ein Paket ungenutzt aufbewahrt werden kann, bevor es abläuft. null = nicht angegeben, NICHT unbegrenzt. Bewusst getrennt von install_by_days: LAGERDAUER und INSTALLATIONSFRIST sind verschieden.

modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_cursorstring
kann null sein

Für die nächste Seite UNVERÄNDERT zurücksenden. null ist das EINZIGE Endsignal. Auch auf eine Seite mit weniger als page_size Einträgen können weitere folgen.

page_sizeinteger

MAXIMALE Datensatzanzahl, die der Server für diese Seite verwendet hat, bereits begrenzt. Nicht die tatsächliche Anzahl in items.

policyobject
kann null sein

Verhalten des Anbieters beim AUFLADEN EINES NEUEN PAKETS. null bedeutet: nicht aufladbar ODER Anbieterrichtlinie noch nicht ermittelt. null verbietet Annahmen; ein Objekt nur mit false würde dagegen garantieren, dass nichts storniert wird.

notestring
kann null sein
—
requires_cancel_firstboolean

Das LAUFENDE Paket wird vor Bestellung des neuen storniert, da dieser Anbieter Pakete nicht stapelt. Informieren Sie Kunden VOR dem Aufladen eines neuen Pakets.

stackingboolean

Ob Laufzeit und Volumen des neuen Pakets zum laufenden addiert werden.

totalinteger · int64
kann null sein

Anzahl passender Pakete; null bei teurer Zählung (international oder ohne market). Vor Schlüsselbeschränkungen und Organisationspreisen berechnet: Katalogtreffer, nicht kaufbare Pakete.

warningsobject[]

Erklärung der Liste; leer ohne Besonderheiten. Geschlossene Codes: filtered_by_key_package_filters, filtered_by_provider_scope, no_price_for_org, price_filter_removed_rows, provider_disabled, capability_missing. Sie stehen für Schlüsselregeln, Anbieterumfang, fehlende Preise, Preisfilter, deaktivierte Anbieter oder fehlende Fähigkeiten. count ist die betroffene Zeilenzahl, sonst null.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
kann null sein
—
messagestring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

Verlauf der eSIM-Erstattungsanträge (nur Mitglieder)

scope esims:readget_esims__id__refund-requests

Erforderlicher Berechtigungsbereich: esims:read.

Nur freigeschaltete Live-Mitgliederschlüssel. Liefert bis zu 50 aktuelle Anträge für die eigene eSIM, neueste zuerst; next_cursor und total sind null. has_open_request zeigt eine offene Prüfung an. Organisations- und Testschlüssel erhalten capability.not_supported.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
has_open_requestboolean
—
itemsobject[]
—
decided_atstring · date-time
kann null sein
—
decision_notestring
kann null sein
—
idstring · uuid
—
order_idstring · uuid
—
order_item_idstring · uuid
—
reasonstring
—
refundedobject
kann null sein
—
requested_atstring · date-time
—
statusstring
—
pendingapprovedrejected
suggestedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_cursorstring
kann null sein

Für die nächste Seite UNVERÄNDERT zurücksenden. null ist das EINZIGE Endsignal. Auch auf eine Seite mit weniger als page_size Einträgen können weitere folgen.

page_sizeinteger
kann null sein

MAXIMALE Datensatzanzahl, die der Server für diese Seite verwendet hat, bereits begrenzt. Nicht die tatsächliche Anzahl in items.

totalinteger · int64
kann null sein

Anzahl der zum Filter passenden Zeilen. null, wenn das Zählen zu aufwendig ist. Nicht zur Erkennung des Endes verwenden; dafür dient next_cursor.

warningsobject[]

Erklärung der Liste; leer ohne Besonderheiten. Geschlossene Codes: filtered_by_key_package_filters, filtered_by_provider_scope, no_price_for_org, price_filter_removed_rows, provider_disabled, capability_missing. Sie stehen für Schlüsselregeln, Anbieterumfang, fehlende Preise, Preisfilter, deaktivierte Anbieter oder fehlende Fähigkeiten. count ist die betroffene Zeilenzahl, sonst null.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
kann null sein
—
messagestring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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-Erstattungsantrag erstellen (nur Mitglieder)

scope esims:writeIdempotency-Key erforderlichpost_esims__id__refund-requests

Erforderlicher Berechtigungsbereich: esims:write.

Nur freigeschaltete Live-Mitgliederschlüssel. Einen Erstattungsantrag für die eigene eSIM mit Idempotency-Key senden; Geld wird erst nach Freigabe übertragen. Organisations- und Testschlüssel erhalten capability.not_supported. Im Testmodus übt die eSIM-Stornierung eine Erstattung ins virtuelle Wallet.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Header (1)

Idempotency-Keystringerforderlich

Vom Client erzeugte Zeichenfolge: Gleicher Schlüssel und gleicher Body werden einmal ausgeführt; Wiederholungen liefern das erste Ergebnis. Gilt je api_key_id (§11): zwei Schlüssel sind getrennte Befehle.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
reasonstring
erforderlich

Erstattungsgrund mit 5 bis 1000 Zeichen nach Entfernen führender und nachfolgender Leerzeichen.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
decided_atstring · date-time
kann null sein
—
decision_notestring
kann null sein
—
idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
order_idstring · uuid
—
order_item_idstring · uuid
—
reasonstring
—
refundedobject
kann null sein
—
requested_atstring · date-time
—
statusstring
—
pendingapprovedrejected
suggestedobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

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
}

Beispiel-Anfragetext

json
{
  "reason": "<reason>"
}
GET/v1/esims/{id}/usage

Verbrauchte Daten

scope esims:readget_esims__id__usage

Erforderlicher Berechtigungsbereich: esims:read.

Liest das verbrauchte und verbleibende Datenvolumen einer eSIM samt Zeitstempel updated_at der Werte (tatsächliche Aktualität). Bei einer eSIM eines Anbieters, der nur auf Anfrage abgefragt wird (derzeit SkyFi), stellt dieser Aufruf einen Scan in die Warteschlange und WARTET bis zu etwa 15 Sekunden auf das Ergebnis; die Antwort kann daher bis zu ~15 Sekunden dauern, und die Werte stammen aus dem soeben beendeten Scan. Schlägt der Scan fehl, liegt der letzte Scan derselben eSIM weniger als 15 Sekunden zurück oder läuft die Wartezeit ab, wird KEIN Fehler zurückgegeben: Es werden die zuletzt gespeicherten Werte geliefert, prüfen Sie updated_at. Andere Anbieter liefern gespeicherte Werte, die regelmäßig aktualisiert werden.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
esim_idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
usageobject
kann null sein

Datenverbrauchsstatistik der eSIM. updated_at = null, wenn noch nie abgefragt.

expires_atstring · date-time
kann null sein
—
remaining_bytesinteger · int64
kann null sein
—
sourcestring
kann null sein
—
pollwebhook
total_bytesinteger · int64
kann null sein
—
updated_atstring · date-time
kann null sein
—
used_bytesinteger · int64
kann null sein
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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
}

Wallet

Wallet-Guthaben

GET/v1/wallet/balance

Wallet-Guthaben

scope wallet:readget_wallet_balance

Erforderlicher Berechtigungsbereich: wallet:read.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
availableobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
balanceobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
credit_limitobject

Geldbetrag: Einheitliches Format überall auf /v1; kein Wechsel zwischen Objekt und bloßer Zahl.

amountinteger · int64

Ganzzahl in der kleinsten Einheit von currency (VND hat keine Untereinheit).

currencystring
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

json
{
  "data": {
    "available": {
      "amount": 273000,
      "currency": "VND"
    },
    "balance": {
      "amount": 273000,
      "currency": "VND"
    },
    "credit_limit": {
      "amount": 273000,
      "currency": "VND"
    },
    "mode": "live"
  },
  "ok": true
}

Webhooks

Webhook-Konfiguration, Zustellprotokoll und Simulator

POST/v1/testmode/webhooks/fail-next

Nächsten Webhook-Fehler simulieren (nur Testschlüssel)

scope webhook:managepost_testmode_webhooks_fail-next

Erforderlicher Berechtigungsbereich: webhook:manage.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
countinteger
—

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
endpoint_idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
testmode_fail_nextinteger
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

json
{
  "data": {
    "endpoint_id": "018f2b00-0000-7000-8000-000000000000",
    "mode": "live",
    "testmode_fail_next": 1
  },
  "ok": true
}

Beispiel-Anfragetext

json
{
  "count": 1
}
POST/v1/testmode/webhooks/fire

Test-Webhook-Ereignis senden (nur Testschlüssel)

scope webhook:managepost_testmode_webhooks_fire

Erforderlicher Berechtigungsbereich: webhook:manage.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
dataobject

Das Feld data der Beispielnutzlast teilweise überschreiben.

event_typestring
erforderlich

Zu simulierender Webhook-Ereignistyp.

order.completedorder.failedesim.deliveredesim.activatedesim.usage_thresholdesim.expiringwallet.low_balancecatalog.updated

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
delivery_idstring · uuid
—
event_idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

json
{
  "data": {
    "delivery_id": "018f2b00-0000-7000-8000-000000000000",
    "event_id": "018f3c00-0000-7000-8000-0000000000b0",
    "mode": "live"
  },
  "ok": true
}

Beispiel-Anfragetext

json
{
  "data": {},
  "event_type": "order.completed"
}
GET/v1/webhooks

Webhook-Endpunkte auflisten

scope webhook:manageget_webhooks

Erforderlicher Berechtigungsbereich: webhook:manage.

Nur Endpoints des aufrufenden Schlüssels. Kein Schlüssel sieht den Endpoint eines anderen, auch nicht innerhalb derselben Organisation (§5.11: Bindung an api_key_id, keine gemeinsame Nutzung durch Live und Test).

Noch nicht eingerichtet: 200 mit leerem Array, kein 404. Die Sammlung existiert immer, auch ohne Einträge.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
itemsobject[]
—
activeboolean

false: Keine neuen Ereignisse nach Pausierung im Dashboard oder Schlüsselwiderruf (Abschaltung in derselben Transaktion). Ein erfolgreicher CHECK_HEALTH bei PUT /v1/webhooks/{id} aktiviert den Endpunkt wieder.

created_atstring · date-time
—
eventsstring[]

Leer = alles empfangen.

idstring · uuid

Endpoint-ID für /v1/webhooks/{id}. Jede Erstellung erzeugt eine NEUE ID. Löschen und Neuanlegen erzeugt einen anderen Endpoint, keine Wiederherstellung des alten.

last_delivery_atstring · date-time
kann null sein

Letzte Zustellung über diesen Endpoint. null = nie zugestellt, nicht fehlgeschlagen. Erster Prüfpunkt, wenn ein Partner keine Webhooks erhält.

secret_last4string

Letzte vier Zeichen des Webhook-Geheimnisses, um den verwendeten Signierschlüssel zu prüfen, ohne das Geheimnis erneut offenzulegen. Vollständig nur EINMAL bei Erstellung sichtbar.

urlstring
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_cursorstring
kann null sein

Für die nächste Seite UNVERÄNDERT zurücksenden. null ist das EINZIGE Endsignal. Auch auf eine Seite mit weniger als page_size Einträgen können weitere folgen.

page_sizeinteger
kann null sein

MAXIMALE Datensatzanzahl, die der Server für diese Seite verwendet hat, bereits begrenzt. Nicht die tatsächliche Anzahl in items.

totalinteger · int64
kann null sein

Anzahl der zum Filter passenden Zeilen. null, wenn das Zählen zu aufwendig ist. Nicht zur Erkennung des Endes verwenden; dafür dient next_cursor.

warningsobject[]

Erklärung der Liste; leer ohne Besonderheiten. Geschlossene Codes: filtered_by_key_package_filters, filtered_by_provider_scope, no_price_for_org, price_filter_removed_rows, provider_disabled, capability_missing. Sie stehen für Schlüsselregeln, Anbieterumfang, fehlende Preise, Preisfilter, deaktivierte Anbieter oder fehlende Fähigkeiten. count ist die betroffene Zeilenzahl, sonst null.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
kann null sein
—
messagestring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

Webhook-Endpunkt erstellen

scope webhook:managepost_webhooks

Erforderlicher Berechtigungsbereich: webhook:manage.

Endpoint für den aufrufenden Schlüssel erstellen. Vor dem Speichern sendet der Server ein signiertes POST-ping an die URL (CHECK_HEALTH). Ohne 2xx wird nichts gespeichert: 422 webhook.health_check_failed. Nur öffentliche https-URLs; interne oder Metadaten-IP: 422 webhook.url_not_allowed.

webhook_secret wird GENAU EINMAL angezeigt und kann nicht erneut gelesen werden. Bei Verlust im Dashboard rotieren; das alte Geheimnis bleibt 24 Stunden parallel gültig (§8.4).

Existiert bereits ein Endpoint, gilt 409 webhook.already_configured, auch bei Pause (is_active: false). Pause erhält URL, Geheimnis und Protokoll. Für einen Neustart erst DELETE /v1/webhooks/{id}, dann neu erstellen.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
eventsstring[]

Leer = alles empfangen. Unbekannter Wert: 400 internal.validation_failed (Feld events).

urlstring · uri
erforderlich
—

Antwort

201Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
activeboolean

false: Keine neuen Ereignisse nach Pausierung im Dashboard oder Schlüsselwiderruf (Abschaltung in derselben Transaktion). Ein erfolgreicher CHECK_HEALTH bei PUT /v1/webhooks/{id} aktiviert den Endpunkt wieder.

created_atstring · date-time
—
eventsstring[]

Leer = alles empfangen.

health_checkstring
—
passed
idstring · uuid

Endpoint-ID für /v1/webhooks/{id}. Jede Erstellung erzeugt eine NEUE ID. Löschen und Neuanlegen erzeugt einen anderen Endpoint, keine Wiederherstellung des alten.

last_delivery_atstring · date-time
kann null sein

Letzte Zustellung über diesen Endpoint. null = nie zugestellt, nicht fehlgeschlagen. Erster Prüfpunkt, wenn ein Partner keine Webhooks erhält.

modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
secret_last4string

Letzte vier Zeichen des Webhook-Geheimnisses, um den verwendeten Signierschlüssel zu prüfen, ohne das Geheimnis erneut offenzulegen. Vollständig nur EINMAL bei Erstellung sichtbar.

urlstring
—
webhook_secretstring

NUR EINMAL bei Erstellung angezeigt (Präfix whsec_).

okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

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
}

Beispiel-Anfragetext

json
{
  "events": [
    "order.completed"
  ],
  "url": "https://partner.example.com/hooks/tke"
}
GET/v1/webhooks/{id}

Einen Webhook-Endpunkt abrufen

scope webhook:manageget_webhooks__id_

Erforderlicher Berechtigungsbereich: webhook:manage.

Pfadparameter (1)

idstringerforderlich

Webhook-Endpunkt-ID (id aus POST /v1/webhooks oder GET /v1/webhooks).

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
activeboolean

false: Keine neuen Ereignisse nach Pausierung im Dashboard oder Schlüsselwiderruf (Abschaltung in derselben Transaktion). Ein erfolgreicher CHECK_HEALTH bei PUT /v1/webhooks/{id} aktiviert den Endpunkt wieder.

created_atstring · date-time
—
eventsstring[]

Leer = alles empfangen.

idstring · uuid

Endpoint-ID für /v1/webhooks/{id}. Jede Erstellung erzeugt eine NEUE ID. Löschen und Neuanlegen erzeugt einen anderen Endpoint, keine Wiederherstellung des alten.

last_delivery_atstring · date-time
kann null sein

Letzte Zustellung über diesen Endpoint. null = nie zugestellt, nicht fehlgeschlagen. Erster Prüfpunkt, wenn ein Partner keine Webhooks erhält.

modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
secret_last4string

Letzte vier Zeichen des Webhook-Geheimnisses, um den verwendeten Signierschlüssel zu prüfen, ohne das Geheimnis erneut offenzulegen. Vollständig nur EINMAL bei Erstellung sichtbar.

urlstring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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}

Webhook-Endpunkt aktualisieren

scope webhook:manageput_webhooks__id_

Erforderlicher Berechtigungsbereich: webhook:manage.

Pfadparameter (1)

idstringerforderlich

Webhook-Endpunkt-ID (id aus POST /v1/webhooks oder GET /v1/webhooks).

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
eventsstring[]

Leer = alles empfangen. Unbekannter Wert: 400 internal.validation_failed (Feld events).

urlstring · uri
erforderlich
—

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
activeboolean

false: Keine neuen Ereignisse nach Pausierung im Dashboard oder Schlüsselwiderruf (Abschaltung in derselben Transaktion). Ein erfolgreicher CHECK_HEALTH bei PUT /v1/webhooks/{id} aktiviert den Endpunkt wieder.

created_atstring · date-time
—
eventsstring[]

Leer = alles empfangen.

idstring · uuid

Endpoint-ID für /v1/webhooks/{id}. Jede Erstellung erzeugt eine NEUE ID. Löschen und Neuanlegen erzeugt einen anderen Endpoint, keine Wiederherstellung des alten.

last_delivery_atstring · date-time
kann null sein

Letzte Zustellung über diesen Endpoint. null = nie zugestellt, nicht fehlgeschlagen. Erster Prüfpunkt, wenn ein Partner keine Webhooks erhält.

modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
secret_last4string

Letzte vier Zeichen des Webhook-Geheimnisses, um den verwendeten Signierschlüssel zu prüfen, ohne das Geheimnis erneut offenzulegen. Vollständig nur EINMAL bei Erstellung sichtbar.

urlstring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

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
}

Beispiel-Anfragetext

json
{
  "events": [
    "order.completed"
  ],
  "url": "https://partner.example.com/hooks/tke"
}
DELETE/v1/webhooks/{id}

Webhook-Endpunkt entfernen (keine neuen Ereignisse mehr empfangen)

scope webhook:managedelete_webhooks__id_

Erforderlicher Berechtigungsbereich: webhook:manage.

Das Entfernen des Endpoints beendet neue Ereignisse für den aufrufenden Schlüssel.

Das webhook_secret geht mit dem Endpoint verloren. Neuanlage mit POST /v1/webhooks erzeugt NEUE id und NEUES Geheimnis; die Verifikation muss angepasst werden. Für vorübergehende Unterbrechung bei gleichem Geheimnis Pause im Dashboard verwenden.

Das vollständige Zustellprotokoll bleibt zum Abgleich erhalten, die Einträge des entfernten Endpoints können aber nicht erneut gesendet werden.

Auf Wiederholung wartende pending-Zustellungen enden mit endpoint_disabled und bleiben im Protokoll (§8.5).

Fremde oder bereits entfernte id: 404 webhook.not_found, niemals stilles 200.

Pfadparameter (1)

idstringerforderlich

Webhook-Endpunkt-ID (id aus POST /v1/webhooks oder GET /v1/webhooks).

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
deleted_atstring · date-time
—
idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

json
{
  "data": {
    "deleted_at": "2026-07-11T08:12:30Z",
    "id": "018f2b00-0000-7000-8000-00000000009a",
    "mode": "live"
  },
  "ok": true
}
GET/v1/webhooks/deliveries

Webhook-Zustellprotokoll

scope webhook:manageget_webhooks_deliveries

Erforderlicher Berechtigungsbereich: webhook:manage.

Query-Parameter (5)

pageintegeroptionalStandard 1min 1

Seitennummer ab 1. Hinter dem Datenende: items: [] und das echte total (200, kein 404).

page_sizeintegeroptionalStandard 50min 1max 200

Datensätze pro Seite. Standard 50, höchstens 200. Höhere Werte werden ohne Fehler auf 200 begrenzt.

event_typestringoptional

Nach Ereignistyp filtern (siehe Webhook-Ereigniskatalog).

statusstringoptional
Erlaubte Werte:pendingdeliveredfailed

Nach Zustellstatus filtern. ?status=failed dient dem abfragebasierten Webhook-Abgleich (§13). Unbekannte Werte werden ohne Fehler IGNORIERT; das vollständige Protokoll wird zurückgegeben.

event_idstring · uuidoptional

Alle Zustellversuche eines Ereignisses, einschließlich Wiederholungen.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
itemsobject[]
—
attemptsinteger
—
created_atstring · date-time
—
event_idstring · uuid

Kennung des EREIGNISSES. Wiederholung erzeugt eine NEUE Zustellung mit GLEICHER event_id. Zur Duplikaterkennung event_id, nicht die Zustell-id verwenden.

event_typestring
—
idstring · uuid
—
last_attempt_atstring · date-time
kann null sein
—
next_retry_atstring · date-time
kann null sein
—
response_codeinteger
kann null sein
—
statusstring
—
pendingdeliveredfailed
webhook_idstring · uuid
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_cursorstring
kann null sein

Für die nächste Seite UNVERÄNDERT zurücksenden. null ist das EINZIGE Endsignal. Auch auf eine Seite mit weniger als page_size Einträgen können weitere folgen.

page_sizeinteger
kann null sein

MAXIMALE Datensatzanzahl, die der Server für diese Seite verwendet hat, bereits begrenzt. Nicht die tatsächliche Anzahl in items.

totalinteger · int64
kann null sein

Anzahl der zum Filter passenden Zeilen. null, wenn das Zählen zu aufwendig ist. Nicht zur Erkennung des Endes verwenden; dafür dient next_cursor.

warningsobject[]

Erklärung der Liste; leer ohne Besonderheiten. Geschlossene Codes: filtered_by_key_package_filters, filtered_by_provider_scope, no_price_for_org, price_filter_removed_rows, provider_disabled, capability_missing. Sie stehen für Schlüsselregeln, Anbieterumfang, fehlende Preise, Preisfilter, deaktivierte Anbieter oder fehlende Fähigkeiten. count ist die betroffene Zeilenzahl, sonst null.

codestring
—
filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing
countinteger · int64
kann null sein
—
messagestring
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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

Webhook erneut senden

scope webhook:managepost_webhooks_deliveries__id__replay

Erforderlicher Berechtigungsbereich: webhook:manage.

Pfadparameter (1)

idstringerforderlich

ID einer Webhook-Zustellung (items[].id aus GET /v1/webhooks/deliveries).

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
attemptsinteger
—
created_atstring · date-time
—
event_idstring · uuid

Kennung des EREIGNISSES. Wiederholung erzeugt eine NEUE Zustellung mit GLEICHER event_id. Zur Duplikaterkennung event_id, nicht die Zustell-id verwenden.

event_typestring
—
idstring · uuid
—
last_attempt_atstring · date-time
kann null sein
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
next_retry_atstring · date-time
kann null sein
—
response_codeinteger
kann null sein
—
statusstring
—
pendingdeliveredfailed
webhook_idstring · uuid
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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
}

Testmodus

Nur Testschlüssel tke_test_ oder tke_member_test_ auf api-testing verwenden. Eigene Test-eSIMs und Daten ohne echtes Guthaben oder Lieferanten simulieren. Der Mitgliedertestmodus unterstützt keine Webhooks, Sammelbestellungen oder Rückerstattungsanträge zur Prüfung.

POST/v1/testmode/esims/{id}/simulate

Lebenszyklus einer Test-eSIM simulieren (nur Testschlüssel)

scope esims:writepost_testmode_esims__id__simulate

Erforderlicher Berechtigungsbereich: esims:write.

Nur Testschlüssel tke_test_ oder tke_member_test_. Ein Szenario auf die eigene Test-eSIM anwenden: Verbrauch, Installation, Ablauf, Drosselung oder simulierte Ablehnung des nächsten Vorgangs. Der Body benötigt mindestens ein Feld; leer ergibt 422 testmode.invalid_scenario. Ergebnisse über GET /v1/esims/{id} und /usage wie im Live-Modus lesen. Tarifwechsel- und Stornoregeln werden gemeinsam genutzt; kein echter Lieferant wird aufgerufen.

Pfadparameter (1)

idstringerforderlich

Stabile ID einer verkauften eSIM, nicht die wiederverwendbare ICCID.

Anfragetexterforderlichapplication/json

FeldTypEinschränkungenBeschreibung
data_used_mbinteger · int64

Bereits verbrauchte Daten in MB (binär, 1 MiB). Werte über dem Kontingent werden begrenzt: Das ist das Verbrauchsszenario, kein Fehler.

expire_atstring · date-time

Einen genauen Ablaufzeitpunkt festlegen (ISO-8601 UTC).

expire_nowboolean

true: Paket sofort ablaufen lassen (esim_status wird expired).

provider_rejectboolean

Anbieterablehnung beim nächsten Vorgang simulieren (Aufladung/Stornierung).

statusstring

TECHNISCHER Lebenszyklus des eSIM-Profils im Vokabular des Anbieters. Anders als der kommerzielle esim_status der eSIM-Endpunkte.

releasedinstalledenableddisabled
throttledboolean

true: Leitung gedrosselt (unbegrenztes Paket nach Verbrauch des Hochgeschwindigkeitsvolumens).

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
cancellableobject

Stornierbarkeit der eSIM und Ablehnungsgrund; vor POST /v1/esims/{order_item_id}/cancel prüfen. Dieser Block ist immer vorhanden, nie null: Er ist die Antwort.

allowedboolean
—
disclosed_atstring · date-time
kann null sein

Zeitpunkt, zu dem der Code unser System verlassen hat (ISO-8601 UTC); null, solange niemand ihn gesehen hat. Nachweis bei strittiger Offenlegung und verweigerter Stornierung.

reasonstring
kann null sein

Stornierungsgrund: code_disclosed (Code versendet oder angezeigt), wrong_status (falscher Zustand), expired (Tarif abgelaufen), not_supported (Funktion nicht angegeben). null bei allowed = true. Wie meta.reason bei esim.not_cancellable.

code_disclosedwrong_statusexpirednot_supportednull
external_refstring
kann null sein

Ihre Bestellreferenz (external_ref beim Kauf), zur Zuordnung der eSIM zu Ihrer Bestellung. null, wenn nicht angegeben.

iccidstring
kann null sein
—
idstring · uuid

eSIM-ID für alle /v1/esims/{id}/…-Pfade, zugleich order_item_id in Webhooks. Liegt nur die ICCID vor, GET /v1/esims?iccid=… aufrufen, um die id zu erhalten.

installobject
kann null sein

Alle zur INSTALLATION dieser eSIM benötigten Daten. null bedeutet NICHT ANGEFORDERT (ohne include=lpa). Hat die eSIM noch keinen Code, ist der Block dagegen mit lpa: null vorhanden.

Eine Antwort mit diesem Block hat immer Cache-Control: no-store.

android_urlstring
kann null sein
—
apnstring
kann null sein

Zugangspunktname (APN), falls die automatische Einrichtung nicht erfolgt. In dieser Version IMMER null: unbekannt, nicht unnötig.

ios_urlstring
kann null sein
—
lpastring
kann null sein

Aktivierungscode der eSIM, ein verkäufliches Geheimnis. Diese Zeichenfolge genügt zum Erzeugen des QR-Codes.

matching_idstring
kann null sein
—
qr_code_base64string
kann null sein

QR-Code als PNG-Daten-URI. Nur mit include=qr auf einem DETAIL-Endpunkt vorhanden, NIE in Listen (ca. 11 KB pro Zeile, also ca. 1,1 MB bei 100 Zeilen). QR-Code stattdessen aus lpa erzeugen.

smdp_addressstring
kann null sein
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
msisdnstring
kann null sein
—
order_idstring · uuid
—
package_codestring
kann null sein

Paket-CODE aus dem Katalog als Zuordnungsschlüssel für diese eSIM. Nur null, wenn das Ursprungspaket aus dem Katalog entfernt wurde.

package_namestring

Lesbarer Anzeigename, der sich ändern kann. NICHT als Abgleichschlüssel verwenden.

planobject
kann null sein

Der aktuell für diese eSIM geltende Tarif.

current_codestring
—
expires_atstring · date-time
kann null sein
—
started_atstring · date-time
kann null sein
—
simulatedobject

Simulierter Zustand nach Anwendung des Szenarios.

esim_statestring

TECHNISCHER Lebenszyklus der Test-eSIM. Live stammt er aus smdp_status; eine Test-eSIM hat kein SM-DP+-Profil, daher steht er hier bei den Testmodus-Feldern.

releasedinstalledenableddisabled
last_viastring

Ablauf, der sie zuletzt GEÄNDERT hat.

open_apisimulateseed
viastring

Ablauf, der diese Test-eSIM ERSTELLT hat.

open_apisimulateseed
smdp_statusstring
kann null sein
—
statusstring
—
updated_atstring · date-time
—
usageobject
kann null sein

Datenverbrauchsstatistik der eSIM. updated_at = null, wenn noch nie abgefragt.

expires_atstring · date-time
kann null sein
—
remaining_bytesinteger · int64
kann null sein
—
sourcestring
kann null sein
—
pollwebhook
total_bytesinteger · int64
kann null sein
—
updated_atstring · date-time
kann null sein
—
used_bytesinteger · int64
kann null sein
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

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
}

Beispiel-Anfragetext

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

Testdaten zurücksetzen (nur Testschlüssel)

scope orders:writepost_testmode_reset

Erforderlicher Berechtigungsbereich: orders:write.

Header (1)

Idempotency-Keystringoptional

Anfragetextoptionalapplication/json

FeldTypEinschränkungenBeschreibung
reasonstring
—

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
esim_states_deletedinteger · int64
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
order_items_deletedinteger · int64
—
orders_deletedinteger · int64
—
wallet_balanceinteger · int64

Test-Wallet-Guthaben NACH dem Zurücksetzen, immer 0. Das Hauptbuch wird NICHT gelöscht; eine Gegenbuchung setzt den Saldo auf null.

okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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"

Beispielantwort

json
{
  "data": {
    "esim_states_deleted": 1,
    "mode": "live",
    "order_items_deleted": 1,
    "orders_deleted": 1,
    "wallet_balance": 1
  },
  "ok": true
}

Beispiel-Anfragetext

json
{
  "reason": "Reset test data"
}
POST/v1/testmode/seed

Beispieldaten im Testmodus anlegen (nur Testschlüssel)

scope orders:writepost_testmode_seed

Erforderlicher Berechtigungsbereich: orders:write.

Header (1)

Idempotency-Keystringoptional

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
already_seededboolean
—
esims_createdinteger
—
modestring

Umgebung des aufrufenden Schlüssels, entsprechend X-TKE-Mode. Organisationen verwenden tke_live_/tke_test_, Mitglieder tke_member_live_/tke_member_test_. Live- und Test-Hosts erzwingen den Schlüsselmodus.

testlive
orders_createdinteger
—
seededboolean
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -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)"

Beispielantwort

json
{
  "data": {
    "already_seeded": true,
    "esims_created": 1,
    "mode": "live",
    "orders_created": 1,
    "seeded": true
  },
  "ok": true
}
GET/v1/testmode/status

Testmodus-Übersicht (nur Testschlüssel)

scope esims:readget_testmode_status

Erforderlicher Berechtigungsbereich: esims:read.

Antwort

200Erfolgreich

Erfolg

FeldTypEinschränkungenBeschreibung
dataobject
erforderlich
—
base_url_testingstring

Host für den Testmodus. Nur Schlüssel mit tke_test_ oder tke_member_test_ verwenden; der Live-Host antwortet mit 403 partner.wrong_host.

capabilitiesobject
—
bulk_ordersboolean
—
cancel_refundboolean
—
refund_requestsboolean
—
webhooksboolean
—
checklistobject[]

Integrationsschritte werden aus tatsächlichen API-Aufrufen desselben Inhabers im Testmodus abgeleitet. Der Webhook-Schritt gilt nur für Organisationen.

doneboolean
—
labelstring
—
last_atstring · date-time
kann null sein
—
stepstring
—
driftobject

Simulation des Datenverbrauchs: Aktivierung, Intervall und letzter Lauf. Schwellenwert-Webhooks werden nur für Organisationen mit Webhook-Unterstützung gesendet.

enabledboolean
—
interval_secsinteger · int64
—
last_run_atstring · date-time
kann null sein
—
modestring
—
testlive
readyboolean

Ob der Schlüsselinhaber alle unterstützten Prüfschritte abgeschlossen hat. Mitglieder benötigen keinen Webhook-Test.

sample_countsobject
—
test_esimsinteger · int64
—
test_ordersinteger · int64
—
seededboolean

Ob der Schlüsselinhaber bereits Testbestellungen und Test-eSIMs besitzt.

test_key_countinteger · int64
—
wallet_balanceinteger · int64

Testguthaben in virtuellen VND für den Schlüsselinhaber, getrennt vom Live-Wallet.

webhookobject
—
endpoint_urlstring
kann null sein
—
fail_nextinteger
—
statusstring
kann null sein
—
okboolean
erforderlich
—
true

Fehlerantworten

Alle Fehler liefern { ok: false, error } mit passendem HTTP-Status, nie 200. Details auf der Fehlercodeseite.

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

Beispielaufruf

Alle Werte TKE-VN-30D-X3K9F sind zu ersetzen. Echte Tarifcodes erhalten Sie über 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: de" \
  -H "X-TKE-Key: $KEY_ID" \
  -H "X-TKE-Timestamp: $TS" \
  -H "X-TKE-Nonce: $NONCE" \
  -H "X-TKE-Signature: $SIG"

Beispielantwort

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
}