Référence API
Tous les endpoints /v1 depuis la spécification générée par le backend : chemins, paramètres, schémas, enums et erreurs correspondent au serveur.
Réel
https://api.tongkhoesim.com/v1Mode test
https://api-testing.tongkhoesim.com/v1Réel : api.tongkhoesim.com / tke_live_ (partenaires) ou tke_member_live_ (membres autorisés). Test : api-testing.tongkhoesim.com / tke_test_ / tke_member_test_ (partenaires / membres).
Conventions communes
Ces 6 règles s’appliquent aux 30 opérations.
Tous les endpoints, GET compris : HMAC-SHA256 avec 4 headers : X-TKE-Key · X-TKE-Timestamp · X-TKE-Nonce · X-TKE-Signature. Aucun accès sans signature.
Même clé et même corps sous 24 h : résultat rejoué sans débit supplémentaire. Par api_key_id. Obligatoire sur 5 opérations : POST /v1/orders, POST /v1/orders/bulk, POST /v1/esims/{id}/cancel, POST /v1/esims/{id}/change-plan, POST /v1/esims/{id}/refund-requests.
Pagination : deux modèles
Les formats et conditions d’arrêt diffèrent : utilisez un traitement adapté à chaque modèle.
Uniquement GET /v1/webhooks/deliveries. Retourne page, page_size et total.
Hors limites : HTTP 200, items: [] et total réel. Arrêtez sur une liste vide ou quand total est atteint.
Utilisé par /v1/catalog/packages, /v1/orders, /v1/esims et /v1/esims/{id}/plan-options. updated_since est réservé à orders et esims.
Arrêtez si next_cursor est absent ou identique au curseur courant. Il peut être omis dans data.
Un curseur malformé repart au début sans erreur pour préserver les anciens clients. Une boucle testant seulement next_cursor !== null peut répéter la première page indéfiniment.
let cursor = null;
const seen = new Set();
while (true) {
const params = new URLSearchParams({ page_size: "200" });
if (cursor) params.set("cursor", cursor);
// callSigned ajoute vos 4 headers X-TKE-* (page HMAC).
const page = await callSigned("GET", `/v1/orders?${params}`);
for (const order of page.data.items) await handle(order);
const next = page.data.next_cursor;
if (!next || next === cursor || seen.has(next)) break;
seen.add(next);
cursor = next;
}Catalogue
Catalogue des forfaits, surface commune aux deux systèmes de fourniture. Le système est un CHAMP (`market`: `domestic` | `international`) et un filtre (`?market=`), pas deux chemins distincts. AUCUNE notion de stock : ni quantité ni niveau `available`/`low`/`out_of_stock`. Le forfait apparaît simplement ; si le fournisseur ne peut livrer, `POST /v1/orders` renvoie directement une erreur (`esim.out_of_stock`…), sans annonce préalable du catalogue. Pagination unique par curseur. `total` n’est calculé que si peu coûteux (filtre sur le système national), sinon `null`.
/v1/catalog/packagesLister les forfaits
catalog:readget_catalog_packagesAutorisation requise: catalog:read.
Catalogue de votre organisation : une surface commune aux deux systèmes de fourniture, filtrée par ?market=.
Seuls les forfaits réellement ACHETABLES apparaissent : publiés, toujours distribués par le fournisseur, autorisés par la clé ET tarifés pour l’organisation. Un forfait sans prix est absent parce qu’il ne peut être commandé.
Pagination par curseur : premier appel sans cursor, puis renvoyer next_cursor à l’identique jusqu’à null. Une page plus courte que page_size n’indique PAS la fin.
capabilities reflète uniquement les déclarations réelles du forfait : sans déclaration, tous les indicateurs valent false. Mieux vaut omettre un bouton possible que promettre un endpoint qui renverrait 422.
Le catalogue ne présente AUCUNE notion de stock, ni quantité ni niveau disponible/épuisé. Si le fournisseur ne peut exécuter la commande, POST /v1/orders renvoie directement une erreur claire (esim.out_of_stock…), sans déduction à partir d’un champ préalable.
Paramètres de query (18)
page_sizeintegerfacultatifpar défaut 50min 1max 100Lignes par page. Valeur par défaut 50, maximum 100. Une valeur supérieure est ramenée à 100 sans erreur.
marketstringfacultatifSystème de fourniture : domestic | international. Une valeur hors liste renvoie internal.invalid_filter (400), et non le catalogue complet : une faute ne doit pas ressembler à un filtre sans effet.
countrystringfacultatifCode pays ISO-3166 alpha-2 (ex. VN). Uniquement les forfaits couvrant ce pays.
regionstringfacultatifIdentifiant de région (par exemple chau-a). Inclut les forfaits couvrant au moins un pays de cette région et tous les forfaits mondiaux.
qstringfacultatifRecherche libre sur le code public et le nom du forfait (partielle, sans distinction de casse).
tagstringfacultatifFiltrer sur UNE étiquette commerciale. La normalisation supprime les espaces superflus, utilise les minuscules et les tirets : Ban Le et ban-le désignent la même étiquette.
data_modelstringfacultatifModèle de données : daily | fixed | unlimited, remplaçant supports_topup. Valeur inconnue : internal.invalid_filter (400).
data_min_bytesinteger · int64facultatifQuota minimum en OCTETS. Les forfaits quotidiens sont comparés sur leur quota PAR JOUR ; les forfaits illimités satisfont TOUJOURS une borne minimale.
data_max_bytesinteger · int64facultatifQuota maximum en OCTETS. Les forfaits illimités sont EXCLUS : un plafond ne peut s’appliquer à l’illimité et laisser passer ces forfaits fausserait une recherche du plus petit forfait.
days_mininteger · int64facultatifValidité minimum en jours.
days_maxinteger · int64facultatifValidité maximum en jours.
price_mininteger · int64facultatifPrix minimum (VND), comparé au prix de VOTRE ORGANISATION. Ce filtre est appliqué APRÈS la pagination serveur : une page plus courte ne signifie donc pas la fin des données.
price_maxinteger · int64facultatifPrix maximum (VND), mêmes règles que price_min.
network_genstringfacultatifUniquement les forfaits compatibles avec cette génération (2g | 3g | 4g | 5g). Une valeur inconnue renvoie internal.invalid_filter (400).
hotspotstringfacultatiftruefalsetrue : partage Wi-Fi déclaré ; false : absence déclarée. Sans déclaration (features.hotspot = null), aucun des deux filtres ne correspond. Accepte seulement les chaînes true et false.
appstringfacultatifUniquement les forfaits DÉCLARANT prendre en charge cette application sur au moins un système (app=tiktok). Une absence de déclaration ne correspond PAS au filtre : le silence n’est pas un engagement. Seul tiktok est accepté ; tout autre code renvoie internal.invalid_filter (400).
voicestringfacultatiftruefalsetrue : voix déclarée ; false : absence de voix déclarée. Aucune déclaration ne correspond à aucun côté. Accepte seulement les chaînes true et false.
cursorstringfacultatifRenvoyez next_cursor sans modification. Arrêtez uniquement à null, pas sur une page courte : restrictions de clé, prix de l'organisation et filtre de prix s'appliquent après pagination. Le curseur contient l'empreinte des filtres : changement ou corruption → catalog.cursor_mismatch (400), pour éviter de fusionner des pages de requêtes différentes.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
items | object[] | — | |
capabilities | object | Capacités après-vente déclarées par le forfait, jamais déduites du type de fournisseur. Sans déclaration, tous les indicateurs sont | |
cancel | object | — | |
restriction | string | peut être null |
code_not_disclosednull |
supported | boolean | — | |
plan_change | object | — | |
policy | object | peut être null | Politique du fournisseur lors de la RECHARGE AVEC UN NOUVEAU FORFAIT. |
… | … | Champs plus imbriqués : consultez le JSON exemple. | |
supported | boolean | — | |
usage_check | object | — | |
supported | boolean | — | |
code | string | Code de forfait de VOTRE catalogue, soit | |
coverage | object | Zones où le forfait fonctionne. | |
countries | string[] | Codes ISO-3166 alpha-2. Tableau vide pour les forfaits mondiaux (sans énumération de 200 pays) et les forfaits sans couverture déclarée. | |
country_count | integer | Nombre de pays couverts, pour trier ou filtrer les forfaits multi-pays sans tout télécharger ni compter vous-même le tableau. | |
group | string | Couverture : pays ( | |
region | string | peut être null | Toujours |
data | object | Quota de données du forfait. Toutes les valeurs sont en OCTETS, pas en Mo/Go, pour éviter un double arrondi. | |
daily_bytes | integer · int64 | peut être null | Quota QUOTIDIEN en octets. |
label | string | Libellé lisible généré par la même fonction que dans toutes nos interfaces. | |
model | string | Mode de calcul : dailyfixedunlimited | |
reset | object | peut être null | Moment de remise à zéro du quota quotidien, important pour une vente en fin de journée. |
at | string | peut être null | Heure de remise à zéro dans le fuseau |
note | string | peut être null | — |
timezone | string | peut être null | Fuseau horaire de la remise à zéro ( |
total_bytes | integer · int64 | peut être null | Quota total pour la période, en octets. |
description | string | peut être null | Description commerciale du forfait. |
description_en | string | peut être null | Description anglaise ; |
features | object | Fonctionnalités du forfait dans un objet typé, pas un tableau : | |
apps | object[] | État de prise en charge par SYSTÈME D’EXPLOITATION. Une application ABSENTE du tableau signifie information inconnue, PAS compatibilité. Seules les entrées présentes sont déclarées par le fournisseur ; actuellement seul TikTok est déclaré. L’absence de drapeau global est volontaire : sur le catalogue réel, 2 465 forfaits prennent en charge TikTok sur iPhone mais PAS sur Android. Un drapeau unique les classerait tous « non », faussant près d’un tiers du catalogue. | |
android | string |
supportedblockedunknown | |
app | string | Code d'application en minuscules (ex. | |
ios | string |
supportedblockedunknown | |
note | string | peut être null | — |
hotspot | boolean | peut être null | Partage Wi-Fi avec d'autres appareils. |
kyc_required | boolean | peut être null | Vérification d’identité (KYC) requise avant utilisation. |
sms | boolean | peut être null | SMS. |
voice | boolean | peut être null | Appels vocaux. |
fup | object | peut être null | Comportement après épuisement du quota à pleine vitesse. |
behavior | string | peut être null |
throttlecutoffnull |
high_speed_bytes | integer · int64 | peut être null | Octets à pleine vitesse avant réduction du débit. |
note | string | peut être null | — |
speed_kbps | integer | peut être null | Débit APRÈS épuisement du quota rapide (kbps). |
market | string | SYSTÈME DE FOURNITURE du forfait, énumération OUVERTE : ignorer les valeurs inconnues. Détermine la présence d’un stock et les opérations après-vente. domesticinternational | |
name | string | — | |
name_en | string | peut être null | Nom anglais ; |
network | object | Réseaux utilisables par le forfait. | |
apn | string | peut être null | APN à saisir manuellement sur certains appareils Android sans configuration automatique. Nécessaire pour rédiger le guide d’installation et éviter des demandes d’assistance. |
carriers | object[] | Opérateurs réseau utilisés par le forfait. Il s’agit d’une caractéristique de l’eSIM (réseau radio), pas du fournisseur. | |
name | string | — | |
generations | string[] | peut être null | Générations réseau utilisables ( |
speed_cap_kbps | integer | peut être null | Plafond de débit du forfait ENTIER (kbps), distinct de |
price | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
tags | string[] | Étiquettes commerciales normalisées (minuscules, tirets), également utilisées par les restrictions de catalogue de votre clé. Aucun forfait n’est étiqueté actuellement ; | |
validity | object | Durée de validité du forfait et début du décompte. | |
days | integer | — | |
install_by_days | integer | peut être null | Délai en jours pour scanner le QR code et installer l’eSIM après achat. |
label | string | — | |
reclaim_on_expiry | boolean | peut être null | Le fournisseur récupère l'abonnement à expiration : recharge et changement deviennent impossibles. Important pour les renouvellements. |
starts_when | string | peut être null | Début du décompte : on_installon_purchasenull |
unused_valid_days | integer | peut être null | Nombre de jours avant expiration si le forfait reste inutilisé en stock. |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_cursor | string | peut être null | Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. |
page_size | integer | peut être null | Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans |
total | integer · int64 | peut être null | Nombre de forfaits correspondant aux filtres ; |
warnings | object[] | Explications de la liste ; vide si rien d'inhabituel. Codes fermés : | |
code | string | —filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing | |
count | integer · int64 | peut être null | — |
message | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedinternal.invalid_filtercatalog.cursor_mismatchpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/catalog/packages/{code}Lire un forfait
catalog:readget_catalog_packages__code_Autorisation requise: catalog:read.
Détail d’UN SEUL forfait par son code public. data contient les MÊMES CHAMPS qu’un élément de GET /v1/catalog/packages, plus mode. Aucun champ propre au détail : un seul type Package suffit.
Un code invisible renvoie partner.package_not_visible (404), identique pour toutes les causes : code inexistant, restrictions de clé, tarif absent pour l’organisation ou lien fournisseur perdu. Distinguer les causes permettrait à un inconnu d’énumérer le catalogue.
Paramètres du chemin (1)
codestringobligatoireCode du forfait, soit code dans le catalogue (public_sku). IMMUABLE même en cas de changement de fournisseur ; utilisable comme clé de rapprochement. Code hors restrictions de clé ou sans prix pour l’organisation : 404 partner.package_not_visible, comme pour un code inexistant.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.package_not_visiblepartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"data": null,
"ok": true
}Commandes
Passer des commandes et rapprocher
/v1/ordersLister les commandes (rapprochement par interrogation)
orders:readget_ordersAutorisation requise: orders:read.
Paramètres de query (5)
page_sizeintegerfacultatifpar défaut 50min 1max 200Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.
statusstringfacultatifcompletedpending_providerprovider_failedcancelledFiltrer par état de commande. Une valeur hors liste renvoie internal.invalid_filter (400).
external_refstringfacultatifFiltrer par la référence envoyée à la validation de commande (correspondance EXACTE).
updated_sincestring · date-timefacultatifDate ISO-8601 UTC. Renvoie les enregistrements où updated_at >= updated_since, dans l’ordre croissant : voie standard de RAPPROCHEMENT PAR INTERROGATION (§8.5), permettant de retrouver l’état même après un webhook manqué. Format incorrect : internal.invalid_filter (400).
cursorstringfacultatifCurseur suivant : renvoyez next_cursor sans modification. Un curseur mal formé relit le début sans erreur pour préserver les intégrations lors des changements d'encodage. Vérifiez que le curseur change pour éviter une boucle infinie sur la première page.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
items | object[] | — | |
created_at | string · date-time | — | |
external_ref | string | peut être null | — |
fulfillment_status | string | —fulfilledpartially_fulfilledunfulfilled | |
id | string · uuid | — | |
item_count | integer · int64 | — | |
net | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
public_ref | string | peut être null | — |
refunded | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
status | string | — | |
total | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
updated_at | string · date-time | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_cursor | string | peut être null | Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. |
page_size | integer | peut être null | Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans |
total | integer · int64 | peut être null | Nombre de lignes correspondant au filtre. |
warnings | object[] | Explications de la liste ; vide si rien d'inhabituel. Codes fermés : | |
code | string | —filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing | |
count | integer · int64 | peut être null | — |
message | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedinternal.invalid_filterpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/ordersCréer une commande (achat direct)
orders:writeIdempotency-Key obligatoirepost_ordersAutorisation requise: orders:write.
Headers (1)
Idempotency-KeystringobligatoireChaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par api_key_id (§11) : deux clés distinctes sont deux commandes.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
external_ref | string | peut être null | Votre propre référence, restituée dans la réponse et les webhooks. |
include | string[] | peut être null | Blocs supplémentaires : |
invoice_buyer | object | peut être null | Clés membre uniquement. Sans données, le flux consommateur ne nécessite aucune organisation ni entité juridique vietnamienne. Les données fournies suivent le validateur personnel : full_name, tax_code personnel de 12 chiffres et règles de paiement pour address/email. Les factures professionnelles sont demandées après achat. Le test valide seulement les données et n’émet aucune facture MISA. |
address | string | peut être null | — |
email | string | peut être null | — |
full_name | string | peut être null | — |
legal_name | string | peut être null | — |
tax_code | string | peut être null | — |
items | object[] | obligatoire | Forfaits et quantités à acheter (50 eSIM maximum). |
package_code | string | obligatoire | Code de forfait de VOTRE catalogue, soit |
quantity | integer | obligatoire | Quantité d’eSIM à acheter pour ce forfait (≥ 1). |
Réponse
Reprise dans les 24 h avec les mêmes Idempotency-Key et corps : corps IDENTIQUE, aucun nouveau débit, en-tête Idempotent-Replayed: true. Une nouvelle commande renvoie 201 ; ne déduisez pas le succès de ce seul statut.
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | Commande partenaire. |
created_at | string · date-time | — | |
external_ref | string | peut être null | — |
fulfillment_status | string | Quantité réellement livrée, axe DISTINCT de fulfilledpartially_fulfilledunfulfilled | |
id | string · uuid | ID de la commande (UUID). | |
idempotent_replay | boolean | peut être null | Vrai si cette requête rejoue un succès antérieur de manière idempotente. |
invoice_no | string | peut être null | Numéro de facture après émission. |
invoice_status | string | État de la facture TVA de la commande. nonependingissuingissuedfailedcancelled | |
items | object[] | — | |
cancellable | object | Possibilité d'annuler l'eSIM et motif du refus ; à lire avant | |
allowed | boolean | — | |
disclosed_at | string · date-time | peut être null | Moment où le code a quitté nos systèmes (ISO-8601 UTC), |
reason | string | peut être null | Motif du refus d'annulation : code_disclosedwrong_statusexpirednot_supportednull |
external_ref | string | peut être null | Votre référence de commande ( |
iccid | string | peut être null | — |
id | string · uuid | ID de l’eSIM, utilisé dans tous les chemins | |
install | object | peut être null | Toutes les données nécessaires à l’INSTALLATION de cette eSIM. Une réponse contenant ce bloc porte toujours |
android_url | string | peut être null | — |
apn | string | peut être null | Nom du point d’accès (APN) à saisir si la configuration automatique échoue. Toujours |
ios_url | string | peut être null | — |
lpa | string | peut être null | Code d’activation de l’eSIM, secret commercialisable. Cette chaîne suffit pour générer votre QR code. |
matching_id | string | peut être null | — |
qr_code_base64 | string | peut être null | QR code en data-URI PNG. Présent seulement avec |
smdp_address | string | peut être null | — |
msisdn | string | peut être null | — |
order_id | string · uuid | — | |
package_code | string | peut être null | CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. |
package_name | string | NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement. | |
plan | object | peut être null | Le forfait actuellement appliqué à cette eSIM. |
current_code | string | — | |
expires_at | string · date-time | peut être null | — |
started_at | string · date-time | peut être null | — |
smdp_status | string | peut être null | — |
status | string | — | |
updated_at | string · date-time | — | |
usage | object | peut être null | Statistiques d'utilisation des données de l'eSIM. |
expires_at | string · date-time | peut être null | — |
remaining_bytes | integer · int64 | peut être null | — |
source | string | peut être null | —pollwebhook |
total_bytes | integer · int64 | peut être null | — |
updated_at | string · date-time | peut être null | — |
used_bytes | integer · int64 | peut être null | — |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
money_settled | boolean | Montant de commande soldé, sans remboursement en cours. | |
net | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
public_ref | string | peut être null | Code court de commande, à utiliser dans les demandes d'assistance. |
refunded | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
status | string | —completedpending_providerprovider_failedcancelled | |
total | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
ok | boolean | obligatoire | —true |
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | Commande partenaire. |
created_at | string · date-time | — | |
external_ref | string | peut être null | — |
fulfillment_status | string | Quantité réellement livrée, axe DISTINCT de fulfilledpartially_fulfilledunfulfilled | |
id | string · uuid | ID de la commande (UUID). | |
idempotent_replay | boolean | peut être null | Vrai si cette requête rejoue un succès antérieur de manière idempotente. |
invoice_no | string | peut être null | Numéro de facture après émission. |
invoice_status | string | État de la facture TVA de la commande. nonependingissuingissuedfailedcancelled | |
items | object[] | — | |
cancellable | object | Possibilité d'annuler l'eSIM et motif du refus ; à lire avant | |
allowed | boolean | — | |
disclosed_at | string · date-time | peut être null | Moment où le code a quitté nos systèmes (ISO-8601 UTC), |
reason | string | peut être null | Motif du refus d'annulation : code_disclosedwrong_statusexpirednot_supportednull |
external_ref | string | peut être null | Votre référence de commande ( |
iccid | string | peut être null | — |
id | string · uuid | ID de l’eSIM, utilisé dans tous les chemins | |
install | object | peut être null | Toutes les données nécessaires à l’INSTALLATION de cette eSIM. Une réponse contenant ce bloc porte toujours |
android_url | string | peut être null | — |
apn | string | peut être null | Nom du point d’accès (APN) à saisir si la configuration automatique échoue. Toujours |
ios_url | string | peut être null | — |
lpa | string | peut être null | Code d’activation de l’eSIM, secret commercialisable. Cette chaîne suffit pour générer votre QR code. |
matching_id | string | peut être null | — |
qr_code_base64 | string | peut être null | QR code en data-URI PNG. Présent seulement avec |
smdp_address | string | peut être null | — |
msisdn | string | peut être null | — |
order_id | string · uuid | — | |
package_code | string | peut être null | CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. |
package_name | string | NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement. | |
plan | object | peut être null | Le forfait actuellement appliqué à cette eSIM. |
current_code | string | — | |
expires_at | string · date-time | peut être null | — |
started_at | string · date-time | peut être null | — |
smdp_status | string | peut être null | — |
status | string | — | |
updated_at | string · date-time | — | |
usage | object | peut être null | Statistiques d'utilisation des données de l'eSIM. |
expires_at | string · date-time | peut être null | — |
remaining_bytes | integer · int64 | peut être null | — |
source | string | peut être null | —pollwebhook |
total_bytes | integer · int64 | peut être null | — |
updated_at | string · date-time | peut être null | — |
used_bytes | integer · int64 | peut être null | — |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
money_settled | boolean | Montant de commande soldé, sans remboursement en cours. | |
net | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
public_ref | string | peut être null | Code court de commande, à utiliser dans les demandes d'assistance. |
refunded | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
status | string | —completedpending_providerprovider_failedcancelled | |
total | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedinternal.invalid_filterpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedwallet.insufficient_balancepartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.package_not_visibleesim.out_of_stockidempotency.conflictinvoice.buyer_invalidpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"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
}Exemple de corps
{
"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
}
]
}/v1/orders/{id}Détail de commande et eSIM
orders:readget_orders__id_Autorisation requise: orders:read.
Paramètres du chemin (1)
idstringobligatoireID de la commande (UUID).
Paramètres de query (1)
includestringfacultatifBlocs supplémentaires séparés par des virgules : lpa retourne install, qr ajoute install.qr_code_base64 (détail uniquement). RETOURNER LE CODE LE DIVULGUE et ferme définitivement l'annulation de chaque eSIM concernée (cancellable.allowed = false, reason = code_disclosed). Demandez-le à la livraison, omettez-le pour conserver l'annulation. Valeur inconnue : internal.invalid_filter (400, field: "include").
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | Commande partenaire. |
created_at | string · date-time | — | |
external_ref | string | peut être null | — |
fulfillment_status | string | Quantité réellement livrée, axe DISTINCT de fulfilledpartially_fulfilledunfulfilled | |
id | string · uuid | ID de la commande (UUID). | |
idempotent_replay | boolean | peut être null | Vrai si cette requête rejoue un succès antérieur de manière idempotente. |
invoice_no | string | peut être null | Numéro de facture après émission. |
invoice_status | string | État de la facture TVA de la commande. nonependingissuingissuedfailedcancelled | |
items | object[] | — | |
cancellable | object | Possibilité d'annuler l'eSIM et motif du refus ; à lire avant | |
allowed | boolean | — | |
disclosed_at | string · date-time | peut être null | Moment où le code a quitté nos systèmes (ISO-8601 UTC), |
reason | string | peut être null | Motif du refus d'annulation : code_disclosedwrong_statusexpirednot_supportednull |
external_ref | string | peut être null | Votre référence de commande ( |
iccid | string | peut être null | — |
id | string · uuid | ID de l’eSIM, utilisé dans tous les chemins | |
install | object | peut être null | Toutes les données nécessaires à l’INSTALLATION de cette eSIM. Une réponse contenant ce bloc porte toujours |
android_url | string | peut être null | — |
apn | string | peut être null | Nom du point d’accès (APN) à saisir si la configuration automatique échoue. Toujours |
ios_url | string | peut être null | — |
lpa | string | peut être null | Code d’activation de l’eSIM, secret commercialisable. Cette chaîne suffit pour générer votre QR code. |
matching_id | string | peut être null | — |
qr_code_base64 | string | peut être null | QR code en data-URI PNG. Présent seulement avec |
smdp_address | string | peut être null | — |
msisdn | string | peut être null | — |
order_id | string · uuid | — | |
package_code | string | peut être null | CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. |
package_name | string | NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement. | |
plan | object | peut être null | Le forfait actuellement appliqué à cette eSIM. |
current_code | string | — | |
expires_at | string · date-time | peut être null | — |
started_at | string · date-time | peut être null | — |
smdp_status | string | peut être null | — |
status | string | — | |
updated_at | string · date-time | — | |
usage | object | peut être null | Statistiques d'utilisation des données de l'eSIM. |
expires_at | string · date-time | peut être null | — |
remaining_bytes | integer · int64 | peut être null | — |
source | string | peut être null | —pollwebhook |
total_bytes | integer · int64 | peut être null | — |
updated_at | string · date-time | peut être null | — |
used_bytes | integer · int64 | peut être null | — |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
money_settled | boolean | Montant de commande soldé, sans remboursement en cours. | |
net | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
public_ref | string | peut être null | Code court de commande, à utiliser dans les demandes d'assistance. |
refunded | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
status | string | —completedpending_providerprovider_failedcancelled | |
total | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedinternal.invalid_filterpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedinternal.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/orders/{id}/resendRenvoyer l’e-mail ou le QR de la commande
orders:writepost_orders__id__resendAutorisation requise: orders:write.
Paramètres du chemin (1)
idstringobligatoireID de la commande (UUID).
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
id | string · uuid | ID de la commande qui vient d’être renvoyée. | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
resent_at | string · date-time | — | |
resent_to | string | Adresse du destinataire partiellement masquée ; l'interface partenaire ne restitue jamais l'adresse e-mail complète du client. | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedorder.not_deliverablepartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"data": {
"id": "018f2b00-0000-7000-8000-00000000009a",
"mode": "live",
"resent_at": "2026-07-11T08:12:30Z",
"resent_to": "ab***@x.com"
},
"ok": true
}/v1/orders/bulkPasser un lot multi-lignes (une commande par ligne)
orders:writeIdempotency-Key obligatoirepost_orders_bulkAutorisation requise: orders:write.
Commander un lot multi-lignes : chaque ligne crée une commande indépendante, suivant le cycle quote→commit de POST /v1/orders.
SUCCÈS PARTIEL : les erreurs d’une ligne ne bloquent pas les autres, et seules les lignes réussies sont débitées. Les erreurs métier (rupture, solde insuffisant, forfait indisponible, seuil anti-abus) figurent dans lines[].error, sans changer le statut HTTP du lot. Même après épuisement du solde, les lignes suivantes sont tentées car elles peuvent coûter moins cher.
Le statut est toujours 200 : résultats par ligne, pas une nouvelle ressource unique (201), et traitement terminé dans la requête, sans 202 ni interrogation.
Réessayer le MÊME Idempotency-Key avec le MÊME corps recharge le lot existant, conserve les résultats terminés et traite uniquement les lignes pending, sans double débit. Si aucune ligne n’a été exécutée, Idempotent-Replayed: true. Même clé avec autre corps : idempotency.conflict.
Deux lots au contenu identique pour une même organisation en 10 minutes sont traités comme un doublon accidentel, même avec des clés différentes : l’ancien lot est renvoyé avec Idempotent-Replayed: true, sans nouveau débit. Pour un nouvel achat réellement identique, changez external_ref : inclus dans le contenu signé, il distingue les lots et facilite le rapprochement. Un lot totalement échoué (aucune commande créée) n’est jamais bloqué ; après 10 minutes l’achat est à nouveau normal.
Les références de ligne sont renvoyées dans lines[].external_ref, celle du lot en tête de réponse.
Aucun LPA/QR dans la réponse : lire GET /v1/orders/{order_id} pour chaque commande.
Headers (1)
Idempotency-KeystringobligatoireChaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par api_key_id (§11) : deux clés distinctes sont deux commandes.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
external_ref | string | peut être null | Référence du lot ENTIER, renvoyée dans la réponse du lot. |
items | object[] | obligatoire | Chaque élément crée une commande indépendante. Limite configurable (100 par défaut) ; dépassement : |
external_ref | string | peut être null | Référence de CETTE ligne, renvoyée à l’identique dans son résultat. |
package_code | string | obligatoire | Code de forfait de VOTRE catalogue, soit |
quantity | integer | obligatoire | — |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | Résultat d’un lot. SUCCÈS PARTIEL : une ligne en échec ne bloque pas les autres ; seules les lignes |
created_at | string · date-time | — | |
external_ref | string | peut être null | — |
failed_lines | integer | — | |
id | string · uuid | — | |
items | object[] | — | |
charged | object | peut être null | — |
error | object | peut être null | Code d'erreur réel |
code | string | — | |
message | string | — | |
external_ref | string | peut être null | — |
line_no | integer | À partir de 1, dans l'ordre exact de l'envoi. | |
order_id | string · uuid | peut être null | — |
package_code | string | — | |
quantity | integer | — | |
status | string | —pendingdonefailed | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
status | string |
pendingcompletedpartialfailed | |
succeeded_lines | integer | — | |
total_charged | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
total_lines | integer | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.package_not_visibleidempotency.conflictinternal.limit_exceededpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"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
}Exemple de corps
{
"external_ref": "batch-2026-001",
"items": [
{
"external_ref": "po-1001",
"package_code": "TKE-VN-30D-X3K9F",
"quantity": 5
}
]
}/v1/orders/bulk/{id}Résultat d’une commande par lot
orders:readget_orders_bulk__id_Autorisation requise: orders:read.
Relire les résultats par ligne d’un lot déjà soumis, pour récupérer une réponse perdue sans renvoyer le corps. Un lot d’une autre organisation ou d’un autre environnement (production/test) renvoie 404.
Paramètres du chemin (1)
idstringobligatoireID du lot (UUID) renvoyé par POST /v1/orders/bulk.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | Résultat d’un lot. SUCCÈS PARTIEL : une ligne en échec ne bloque pas les autres ; seules les lignes |
created_at | string · date-time | — | |
external_ref | string | peut être null | — |
failed_lines | integer | — | |
id | string · uuid | — | |
items | object[] | — | |
charged | object | peut être null | — |
error | object | peut être null | Code d'erreur réel |
code | string | — | |
message | string | — | |
external_ref | string | peut être null | — |
line_no | integer | À partir de 1, dans l'ordre exact de l'envoi. | |
order_id | string · uuid | peut être null | — |
package_code | string | — | |
quantity | integer | — | |
status | string | —pendingdonefailed | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
status | string |
pendingcompletedpartialfailed | |
succeeded_lines | integer | — | |
total_charged | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
total_lines | integer | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedinternal.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
Enregistrements eSIM, consommation, recharge et annulation
/v1/esimsLister les eSIM (rapprochement par interrogation)
esims:readget_esimsAutorisation requise: esims:read.
Paramètres de query (8)
page_sizeintegerfacultatifpar défaut 50min 1max 200Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.
statusstringfacultatifpendingavailablereservedsolddeliveredin_useused_upexpiredsuspendedcancelledprovider_failedFiltrer par esim_status. Une valeur hors liste renvoie internal.invalid_filter (400), et non une liste vide : une faute comme canceled au lieu de cancelled ne doit pas passer pour une absence d’eSIM.
order_idstring · uuidfacultatifUniquement les eSIM d’une même commande.
iccidstringfacultatifFiltrer par ICCID (chaîne, correspondance exacte).
external_refstringfacultatifToutes les eSIM des commandes portant votre référence external_ref (correspondance exacte).
updated_sincestring · date-timefacultatifDate ISO-8601 UTC. Renvoie les enregistrements où updated_at >= updated_since, dans l’ordre croissant : voie standard de RAPPROCHEMENT PAR INTERROGATION (§8.5), permettant de retrouver l’état même après un webhook manqué. Format incorrect : internal.invalid_filter (400).
cursorstringfacultatifCurseur suivant : renvoyez next_cursor sans modification. Un curseur mal formé relit le début sans erreur pour préserver les intégrations lors des changements d'encodage. Vérifiez que le curseur change pour éviter une boucle infinie sur la première page.
includestringfacultatifBlocs supplémentaires séparés par des virgules : lpa retourne install, qr ajoute install.qr_code_base64 (détail uniquement). RETOURNER LE CODE LE DIVULGUE et ferme définitivement l'annulation de chaque eSIM concernée (cancellable.allowed = false, reason = code_disclosed). Demandez-le à la livraison, omettez-le pour conserver l'annulation. Valeur inconnue : internal.invalid_filter (400, field: "include").
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
items | object[] | — | |
cancellable | object | Possibilité d'annuler l'eSIM et motif du refus ; à lire avant | |
allowed | boolean | — | |
disclosed_at | string · date-time | peut être null | Moment où le code a quitté nos systèmes (ISO-8601 UTC), |
reason | string | peut être null | Motif du refus d'annulation : code_disclosedwrong_statusexpirednot_supportednull |
external_ref | string | peut être null | Votre référence de commande ( |
iccid | string | peut être null | — |
id | string · uuid | ID de l’eSIM, utilisé dans tous les chemins | |
install | object | peut être null | Toutes les données nécessaires à l’INSTALLATION de cette eSIM. Une réponse contenant ce bloc porte toujours |
android_url | string | peut être null | — |
apn | string | peut être null | Nom du point d’accès (APN) à saisir si la configuration automatique échoue. Toujours |
ios_url | string | peut être null | — |
lpa | string | peut être null | Code d’activation de l’eSIM, secret commercialisable. Cette chaîne suffit pour générer votre QR code. |
matching_id | string | peut être null | — |
qr_code_base64 | string | peut être null | QR code en data-URI PNG. Présent seulement avec |
smdp_address | string | peut être null | — |
msisdn | string | peut être null | — |
order_id | string · uuid | — | |
package_code | string | peut être null | CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. |
package_name | string | NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement. | |
plan | object | peut être null | Le forfait actuellement appliqué à cette eSIM. |
current_code | string | — | |
expires_at | string · date-time | peut être null | — |
started_at | string · date-time | peut être null | — |
smdp_status | string | peut être null | — |
status | string | — | |
updated_at | string · date-time | — | |
usage | object | peut être null | Statistiques d'utilisation des données de l'eSIM. |
expires_at | string · date-time | peut être null | — |
remaining_bytes | integer · int64 | peut être null | — |
source | string | peut être null | —pollwebhook |
total_bytes | integer · int64 | peut être null | — |
updated_at | string · date-time | peut être null | — |
used_bytes | integer · int64 | peut être null | — |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_cursor | string | peut être null | Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. |
page_size | integer | peut être null | Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans |
total | integer · int64 | peut être null | Nombre de lignes correspondant au filtre. |
warnings | object[] | Explications de la liste ; vide si rien d'inhabituel. Codes fermés : | |
code | string | —filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing | |
count | integer · int64 | peut être null | — |
message | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedinternal.invalid_filterpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/esims/{id}Lire une eSIM
esims:readget_esims__id_Autorisation requise: esims:read.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Paramètres de query (1)
includestringfacultatifBlocs supplémentaires séparés par des virgules : lpa retourne install, qr ajoute install.qr_code_base64 (détail uniquement). RETOURNER LE CODE LE DIVULGUE et ferme définitivement l'annulation de chaque eSIM concernée (cancellable.allowed = false, reason = code_disclosed). Demandez-le à la livraison, omettez-le pour conserver l'annulation. Valeur inconnue : internal.invalid_filter (400, field: "include").
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
cancellable | object | Possibilité d'annuler l'eSIM et motif du refus ; à lire avant | |
allowed | boolean | — | |
disclosed_at | string · date-time | peut être null | Moment où le code a quitté nos systèmes (ISO-8601 UTC), |
reason | string | peut être null | Motif du refus d'annulation : code_disclosedwrong_statusexpirednot_supportednull |
external_ref | string | peut être null | Votre référence de commande ( |
iccid | string | peut être null | — |
id | string · uuid | ID de l’eSIM, utilisé dans tous les chemins | |
install | object | peut être null | Toutes les données nécessaires à l’INSTALLATION de cette eSIM. Une réponse contenant ce bloc porte toujours |
android_url | string | peut être null | — |
apn | string | peut être null | Nom du point d’accès (APN) à saisir si la configuration automatique échoue. Toujours |
ios_url | string | peut être null | — |
lpa | string | peut être null | Code d’activation de l’eSIM, secret commercialisable. Cette chaîne suffit pour générer votre QR code. |
matching_id | string | peut être null | — |
qr_code_base64 | string | peut être null | QR code en data-URI PNG. Présent seulement avec |
smdp_address | string | peut être null | — |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
msisdn | string | peut être null | — |
order_id | string · uuid | — | |
package_code | string | peut être null | CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. |
package_name | string | NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement. | |
plan | object | peut être null | Le forfait actuellement appliqué à cette eSIM. |
current_code | string | — | |
expires_at | string · date-time | peut être null | — |
started_at | string · date-time | peut être null | — |
smdp_status | string | peut être null | — |
status | string | — | |
updated_at | string · date-time | — | |
usage | object | peut être null | Statistiques d'utilisation des données de l'eSIM. |
expires_at | string · date-time | peut être null | — |
remaining_bytes | integer · int64 | peut être null | — |
source | string | peut être null | —pollwebhook |
total_bytes | integer · int64 | peut être null | — |
updated_at | string · date-time | peut être null | — |
used_bytes | integer · int64 | peut être null | — |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedinternal.invalid_filterpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedinternal.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/esims/{id}/cancelAnnuler une eSIM inutilisée
esims:writeIdempotency-Key obligatoirepost_esims__id__cancelAutorisation requise: esims:write.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Headers (1)
Idempotency-KeystringobligatoireChaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par api_key_id (§11) : deux clés distinctes sont deux commandes.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
cancelled_at | string · date-time | — | |
esim_id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
refund_suggested | boolean |
| |
refunded | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
status | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedesim.not_cancellableidempotency.conflictcapability.not_supportedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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)"Exemple de réponse
{
"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
}/v1/esims/{id}/change-planRecharger une eSIM
esims:writeIdempotency-Key obligatoirepost_esims__id__change-planAutorisation requise: esims:write.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Headers (1)
Idempotency-KeystringobligatoireChaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par api_key_id (§11) : deux clés distinctes sont deux commandes.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
package_code | string | obligatoire | Code de forfait de VOTRE catalogue, soit |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | Résultat du changement de forfait eSIM. |
charged | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
current_plan | object | — | |
code | string | — | |
expires_at | string · date-time | peut être null | — |
started_at | string · date-time | peut être null | — |
esim_id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
order_id | string · uuid | — | |
previous_plan | object | peut être null | — |
cancelled_at | string · date-time | peut être null | — |
code | string | — | |
remaining_bytes_forfeited | integer · int64 | peut être null | — |
warning | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedwallet.insufficient_balancepartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.package_not_visibleesim.topup_not_supportedesim.topup_provider_mismatchesim.not_eligibleidempotency.conflictcapability.not_supportedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"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
}Exemple de corps
{
"package_code": "TKE-VN-7D-P4M2Q"
}/v1/esims/{id}/plan-optionsForfaits de recharge valides pour cette eSIM
esims:readget_esims__id__plan-optionsAutorisation requise: esims:read.
Renvoie les recharges valides pour CETTE eSIM. Le serveur déduit le fournisseur de son id et ne renvoie que ses forfaits. Aucun provider_id n’est exposé sur /v1 : ce filtrage empêche structurellement de choisir un forfait d’un autre fournisseur. Envoyer à POST /v1/esims/{id}/change-plan un code absent de cette liste renvoie 409 esim.topup_provider_mismatch.
Une eSIM sans fournisseur associé (import brut ou origine inconnue) renvoie une liste vide (total: 0), résultat normal et non une erreur.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Paramètres de query (2)
page_sizeintegerfacultatifpar défaut 50min 1max 200Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.
cursorstringfacultatifCurseur suivant : renvoyez next_cursor sans modification. Un curseur mal formé relit le début sans erreur pour préserver les intégrations lors des changements d'encodage. Vérifiez que le curseur change pour éviter une boucle infinie sur la première page.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | Forfaits de recharge disponibles pour cette eSIM, filtrés par son fournisseur. Chaque entrée est un |
items | object[] | — | |
capabilities | object | Capacités après-vente déclarées par le forfait, jamais déduites du type de fournisseur. Sans déclaration, tous les indicateurs sont | |
cancel | object | — | |
restriction | string | peut être null |
code_not_disclosednull |
supported | boolean | — | |
plan_change | object | — | |
policy | object | peut être null | Politique du fournisseur lors de la RECHARGE AVEC UN NOUVEAU FORFAIT. |
… | … | Champs plus imbriqués : consultez le JSON exemple. | |
supported | boolean | — | |
usage_check | object | — | |
supported | boolean | — | |
code | string | Code de forfait de VOTRE catalogue, soit | |
coverage | object | Zones où le forfait fonctionne. | |
countries | string[] | Codes ISO-3166 alpha-2. Tableau vide pour les forfaits mondiaux (sans énumération de 200 pays) et les forfaits sans couverture déclarée. | |
country_count | integer | Nombre de pays couverts, pour trier ou filtrer les forfaits multi-pays sans tout télécharger ni compter vous-même le tableau. | |
group | string | Couverture : pays ( | |
region | string | peut être null | Toujours |
data | object | Quota de données du forfait. Toutes les valeurs sont en OCTETS, pas en Mo/Go, pour éviter un double arrondi. | |
daily_bytes | integer · int64 | peut être null | Quota QUOTIDIEN en octets. |
label | string | Libellé lisible généré par la même fonction que dans toutes nos interfaces. | |
model | string | Mode de calcul : dailyfixedunlimited | |
reset | object | peut être null | Moment de remise à zéro du quota quotidien, important pour une vente en fin de journée. |
at | string | peut être null | Heure de remise à zéro dans le fuseau |
note | string | peut être null | — |
timezone | string | peut être null | Fuseau horaire de la remise à zéro ( |
total_bytes | integer · int64 | peut être null | Quota total pour la période, en octets. |
description | string | peut être null | Description commerciale du forfait. |
description_en | string | peut être null | Description anglaise ; |
features | object | Fonctionnalités du forfait dans un objet typé, pas un tableau : | |
apps | object[] | État de prise en charge par SYSTÈME D’EXPLOITATION. Une application ABSENTE du tableau signifie information inconnue, PAS compatibilité. Seules les entrées présentes sont déclarées par le fournisseur ; actuellement seul TikTok est déclaré. L’absence de drapeau global est volontaire : sur le catalogue réel, 2 465 forfaits prennent en charge TikTok sur iPhone mais PAS sur Android. Un drapeau unique les classerait tous « non », faussant près d’un tiers du catalogue. | |
android | string |
supportedblockedunknown | |
app | string | Code d'application en minuscules (ex. | |
ios | string |
supportedblockedunknown | |
note | string | peut être null | — |
hotspot | boolean | peut être null | Partage Wi-Fi avec d'autres appareils. |
kyc_required | boolean | peut être null | Vérification d’identité (KYC) requise avant utilisation. |
sms | boolean | peut être null | SMS. |
voice | boolean | peut être null | Appels vocaux. |
fup | object | peut être null | Comportement après épuisement du quota à pleine vitesse. |
behavior | string | peut être null |
throttlecutoffnull |
high_speed_bytes | integer · int64 | peut être null | Octets à pleine vitesse avant réduction du débit. |
note | string | peut être null | — |
speed_kbps | integer | peut être null | Débit APRÈS épuisement du quota rapide (kbps). |
market | string | SYSTÈME DE FOURNITURE du forfait, énumération OUVERTE : ignorer les valeurs inconnues. Détermine la présence d’un stock et les opérations après-vente. domesticinternational | |
name | string | — | |
name_en | string | peut être null | Nom anglais ; |
network | object | Réseaux utilisables par le forfait. | |
apn | string | peut être null | APN à saisir manuellement sur certains appareils Android sans configuration automatique. Nécessaire pour rédiger le guide d’installation et éviter des demandes d’assistance. |
carriers | object[] | Opérateurs réseau utilisés par le forfait. Il s’agit d’une caractéristique de l’eSIM (réseau radio), pas du fournisseur. | |
name | string | — | |
generations | string[] | peut être null | Générations réseau utilisables ( |
speed_cap_kbps | integer | peut être null | Plafond de débit du forfait ENTIER (kbps), distinct de |
price | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
tags | string[] | Étiquettes commerciales normalisées (minuscules, tirets), également utilisées par les restrictions de catalogue de votre clé. Aucun forfait n’est étiqueté actuellement ; | |
validity | object | Durée de validité du forfait et début du décompte. | |
days | integer | — | |
install_by_days | integer | peut être null | Délai en jours pour scanner le QR code et installer l’eSIM après achat. |
label | string | — | |
reclaim_on_expiry | boolean | peut être null | Le fournisseur récupère l'abonnement à expiration : recharge et changement deviennent impossibles. Important pour les renouvellements. |
starts_when | string | peut être null | Début du décompte : on_installon_purchasenull |
unused_valid_days | integer | peut être null | Nombre de jours avant expiration si le forfait reste inutilisé en stock. |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_cursor | string | peut être null | Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. |
page_size | integer | Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans | |
policy | object | peut être null | Politique du fournisseur lors de la RECHARGE AVEC UN NOUVEAU FORFAIT. |
note | string | peut être null | — |
requires_cancel_first | boolean | Le forfait EN COURS est annulé avant l’achat du nouveau : ce fournisseur ne cumule pas les forfaits. Informez le client AVANT qu’il lance la recharge d’un nouveau forfait. | |
stacking | boolean | Indique si durée et quota du nouveau forfait s'ajoutent au forfait en cours. | |
total | integer · int64 | peut être null | Nombre de forfaits correspondant aux filtres ; |
warnings | object[] | Explications de la liste ; vide si rien d'inhabituel. Codes fermés : | |
code | string | —filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing | |
count | integer · int64 | peut être null | — |
message | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedinternal.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/esims/{id}/refund-requestsHistorique des demandes de remboursement eSIM (membres uniquement)
esims:readget_esims__id__refund-requestsAutorisation requise: esims:read.
Clés membre réelles autorisées uniquement. Renvoie au maximum 50 demandes pour l’eSIM du membre, les plus récentes d’abord ; next_cursor et total valent null. has_open_request signale une validation en attente. Les clés organisation et test reçoivent capability.not_supported.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
has_open_request | boolean | — | |
items | object[] | — | |
decided_at | string · date-time | peut être null | — |
decision_note | string | peut être null | — |
id | string · uuid | — | |
order_id | string · uuid | — | |
order_item_id | string · uuid | — | |
reason | string | — | |
refunded | object | peut être null | — |
requested_at | string · date-time | — | |
status | string | —pendingapprovedrejected | |
suggested | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_cursor | string | peut être null | Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. |
page_size | integer | peut être null | Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans |
total | integer · int64 | peut être null | Nombre de lignes correspondant au filtre. |
warnings | object[] | Explications de la liste ; vide si rien d'inhabituel. Codes fermés : | |
code | string | —filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing | |
count | integer · int64 | peut être null | — |
message | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedinternal.not_foundcapability.not_supportedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/esims/{id}/refund-requestsCréer une demande de remboursement eSIM (membres uniquement)
esims:writeIdempotency-Key obligatoirepost_esims__id__refund-requestsAutorisation requise: esims:write.
Clés membre réelles autorisées uniquement. Soumettez une demande pour l’eSIM du membre avec Idempotency-Key ; aucun transfert avant validation interne. Les clés organisation ou test reçoivent capability.not_supported. En test, annulez une eSIM pour essayer le remboursement virtuel.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Headers (1)
Idempotency-KeystringobligatoireChaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par api_key_id (§11) : deux clés distinctes sont deux commandes.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
reason | string | obligatoire | Motif du remboursement, de 5 à 1000 caractères après suppression des espaces en début et fin. |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
decided_at | string · date-time | peut être null | — |
decision_note | string | peut être null | — |
id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
order_id | string · uuid | — | |
order_item_id | string · uuid | — | |
reason | string | — | |
refunded | object | peut être null | — |
requested_at | string · date-time | — | |
status | string | —pendingapprovedrejected | |
suggested | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedinternal.not_foundesim.refund_not_eligibleesim.refund_request_pendingidempotency.conflictcapability.not_supportedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"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
}Exemple de corps
{
"reason": "<reason>"
}/v1/esims/{id}/usageDonnées utilisées
esims:readget_esims__id__usageAutorisation requise: esims:read.
Lit les données consommées et restantes d’une eSIM, avec l’horodatage updated_at des chiffres (fraîcheur réelle). Pour une eSIM d’un fournisseur lu uniquement à la demande (actuellement SkyFi), cet appel met un scan en file d’attente et ATTEND le résultat jusqu’à environ 15 secondes ; la réponse peut donc prendre jusqu’à ~15 secondes et les chiffres renvoyés proviennent du scan qui vient de se terminer. Si le scan échoue, si l’eSIM a été scannée depuis moins de 15 secondes ou si l’attente expire, AUCUNE erreur n’est renvoyée : les derniers chiffres enregistrés sont renvoyés, vérifiez updated_at. Les autres fournisseurs renvoient les chiffres enregistrés, actualisés périodiquement.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
esim_id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
usage | object | peut être null | Statistiques d'utilisation des données de l'eSIM. |
expires_at | string · date-time | peut être null | — |
remaining_bytes | integer · int64 | peut être null | — |
source | string | peut être null | —pollwebhook |
total_bytes | integer · int64 | peut être null | — |
updated_at | string · date-time | peut être null | — |
used_bytes | integer · int64 | peut être null | — |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedcapability.not_supportedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}Portefeuille
Solde du portefeuille
/v1/wallet/balanceSolde du portefeuille
wallet:readget_wallet_balanceAutorisation requise: wallet:read.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
available | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
balance | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
credit_limit | object | Montant monétaire : même structure partout sur | |
amount | integer · int64 | Entier dans la plus petite unité de | |
currency | string | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"data": {
"available": {
"amount": 273000,
"currency": "VND"
},
"balance": {
"amount": 273000,
"currency": "VND"
},
"credit_limit": {
"amount": 273000,
"currency": "VND"
},
"mode": "live"
},
"ok": true
}Webhooks
Configuration webhook, journal d'envoi et simulateur
/v1/testmode/webhooks/fail-nextSimuler le prochain échec webhook (clés de test uniquement)
webhook:managepost_testmode_webhooks_fail-nextAutorisation requise: webhook:manage.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
count | integer | — |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
endpoint_id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
testmode_fail_next | integer | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbiddeninternal.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"data": {
"endpoint_id": "018f2b00-0000-7000-8000-000000000000",
"mode": "live",
"testmode_fail_next": 1
},
"ok": true
}Exemple de corps
{
"count": 1
}/v1/testmode/webhooks/fireEnvoyer un événement webhook de test (clés de test uniquement)
webhook:managepost_testmode_webhooks_fireAutorisation requise: webhook:manage.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | Remplacer partiellement le champ | |
event_type | string | obligatoire | Type d'événement webhook à simuler. order.completedorder.failedesim.deliveredesim.activatedesim.usage_thresholdesim.expiringwallet.low_balancecatalog.updated |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
delivery_id | string · uuid | — | |
event_id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbiddenwebhook.not_configuredinternal.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"data": {
"delivery_id": "018f2b00-0000-7000-8000-000000000000",
"event_id": "018f3c00-0000-7000-8000-0000000000b0",
"mode": "live"
},
"ok": true
}Exemple de corps
{
"data": {},
"event_type": "order.completed"
}/v1/webhooksLister les points de terminaison webhook
webhook:manageget_webhooksAutorisation requise: webhook:manage.
Endpoints de la clé appelante uniquement. Aucune clé ne voit l’endpoint d’une autre, même dans la même organisation (§5.11 : liaison à api_key_id, sans partage entre production et test).
Sans configuration : 200 avec un tableau vide, pas 404. La collection existe toujours, même sans élément.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
items | object[] | — | |
active | boolean |
| |
created_at | string · date-time | — | |
events | string[] | Vide = tout recevoir. | |
id | string · uuid | ID de l’endpoint pour | |
last_delivery_at | string · date-time | peut être null | Dernier envoi via cet endpoint. |
secret_last4 | string | Quatre derniers caractères du secret webhook, pour vérifier que votre secret correspond à celui utilisé pour signer sans le révéler. Le secret complet n’est affiché qu’UNE FOIS, à la création. | |
url | string | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_cursor | string | peut être null | Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. |
page_size | integer | peut être null | Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans |
total | integer · int64 | peut être null | Nombre de lignes correspondant au filtre. |
warnings | object[] | Explications de la liste ; vide si rien d'inhabituel. Codes fermés : | |
code | string | —filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing | |
count | integer · int64 | peut être null | — |
message | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/webhooksCréer un endpoint webhook
webhook:managepost_webhooksAutorisation requise: webhook:manage.
Créer un endpoint pour la clé appelante. Avant sauvegarde, le serveur envoie un POST signé ping à l’URL (CHECK_HEALTH). Sans 2xx, rien n’est sauvegardé : 422 webhook.health_check_failed. URL publique https obligatoire ; adresse interne ou de métadonnées : 422 webhook.url_not_allowed.
webhook_secret n’est affiché qu’UNE FOIS et ne peut être relu. En cas de perte, effectuer une rotation dans le tableau de bord ; l’ancien secret reste valide pendant 24 h (§8.4).
Un endpoint existe déjà : 409 webhook.already_configured, même en pause (is_active: false). La pause conserve URL, secret et journal. Pour recommencer, utiliser DELETE /v1/webhooks/{id}, puis créer un nouvel endpoint.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
events | string[] | Vide = tout recevoir. Valeur inconnue : 400 | |
url | string · uri | obligatoire | — |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
active | boolean |
| |
created_at | string · date-time | — | |
events | string[] | Vide = tout recevoir. | |
health_check | string | —passed | |
id | string · uuid | ID de l’endpoint pour | |
last_delivery_at | string · date-time | peut être null | Dernier envoi via cet endpoint. |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
secret_last4 | string | Quatre derniers caractères du secret webhook, pour vérifier que votre secret correspond à celui utilisé pour signer sans le révéler. Le secret complet n’est affiché qu’UNE FOIS, à la création. | |
url | string | — | |
webhook_secret | string | Affiché UNE SEULE FOIS à la création (préfixe | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedwebhook.already_configuredwebhook.health_check_failedwebhook.url_not_allowedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"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
}Exemple de corps
{
"events": [
"order.completed"
],
"url": "https://partner.example.com/hooks/tke"
}/v1/webhooks/{id}Lire un point de terminaison webhook
webhook:manageget_webhooks__id_Autorisation requise: webhook:manage.
Paramètres du chemin (1)
idstringobligatoireIdentifiant de l'endpoint webhook (id de POST /v1/webhooks ou GET /v1/webhooks).
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
active | boolean |
| |
created_at | string · date-time | — | |
events | string[] | Vide = tout recevoir. | |
id | string · uuid | ID de l’endpoint pour | |
last_delivery_at | string · date-time | peut être null | Dernier envoi via cet endpoint. |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
secret_last4 | string | Quatre derniers caractères du secret webhook, pour vérifier que votre secret correspond à celui utilisé pour signer sans le révéler. Le secret complet n’est affiché qu’UNE FOIS, à la création. | |
url | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedwebhook.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/webhooks/{id}Modifier un endpoint webhook
webhook:manageput_webhooks__id_Autorisation requise: webhook:manage.
Paramètres du chemin (1)
idstringobligatoireIdentifiant de l'endpoint webhook (id de POST /v1/webhooks ou GET /v1/webhooks).
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
events | string[] | Vide = tout recevoir. Valeur inconnue : 400 | |
url | string · uri | obligatoire | — |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
active | boolean |
| |
created_at | string · date-time | — | |
events | string[] | Vide = tout recevoir. | |
id | string · uuid | ID de l’endpoint pour | |
last_delivery_at | string · date-time | peut être null | Dernier envoi via cet endpoint. |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
secret_last4 | string | Quatre derniers caractères du secret webhook, pour vérifier que votre secret correspond à celui utilisé pour signer sans le révéler. Le secret complet n’est affiché qu’UNE FOIS, à la création. | |
url | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedwebhook.not_foundwebhook.health_check_failedwebhook.url_not_allowedpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"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
}Exemple de corps
{
"events": [
"order.completed"
],
"url": "https://partner.example.com/hooks/tke"
}/v1/webhooks/{id}Supprimer un endpoint webhook (arrêter les nouveaux événements)
webhook:managedelete_webhooks__id_Autorisation requise: webhook:manage.
Supprimer l’endpoint arrête les nouveaux événements de la clé appelante.
Le webhook_secret est perdu avec l’endpoint. Une recréation par POST /v1/webhooks génère un NOUVEL id et un NOUVEAU secret : adaptez votre vérification. Pour suspendre temporairement en conservant le secret, utilisez Pause dans le tableau de bord.
Le journal complet reste disponible pour le rapprochement, mais les envois de cet endpoint supprimé ne peuvent plus être rejoués.
Les envois pending en attente de réessai s’arrêtent avec endpoint_disabled et restent journalisés (§8.5).
Un id d’une autre clé ou déjà supprimé renvoie 404 webhook.not_found, jamais un 200 silencieux.
Paramètres du chemin (1)
idstringobligatoireIdentifiant de l'endpoint webhook (id de POST /v1/webhooks ou GET /v1/webhooks).
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
deleted_at | string · date-time | — | |
id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedwebhook.not_foundpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"data": {
"deleted_at": "2026-07-11T08:12:30Z",
"id": "018f2b00-0000-7000-8000-00000000009a",
"mode": "live"
},
"ok": true
}/v1/webhooks/deliveriesJournal des envois webhook
webhook:manageget_webhooks_deliveriesAutorisation requise: webhook:manage.
Paramètres de query (5)
pageintegerfacultatifpar défaut 1min 1Numéro de page à partir de 1. Au-delà des données : items: [] et le vrai total (200, pas 404).
page_sizeintegerfacultatifpar défaut 50min 1max 200Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.
event_typestringfacultatifFiltrer par type d’événement (voir le catalogue des événements webhook).
statusstringfacultatifpendingdeliveredfailedFiltrer par état d’envoi. ?status=failed sert au rapprochement des webhooks par interrogation (§13). Une valeur inconnue est IGNORÉE sans erreur : le journal complet est renvoyé.
event_idstring · uuidfacultatifToutes les tentatives d'envoi d'un événement, reprises incluses.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
items | object[] | — | |
attempts | integer | — | |
created_at | string · date-time | — | |
event_id | string · uuid | Identifiant de l’ÉVÉNEMENT. Un rejeu crée un NOUVEL envoi avec le MÊME | |
event_type | string | — | |
id | string · uuid | — | |
last_attempt_at | string · date-time | peut être null | — |
next_retry_at | string · date-time | peut être null | — |
response_code | integer | peut être null | — |
status | string | —pendingdeliveredfailed | |
webhook_id | string · uuid | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_cursor | string | peut être null | Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. |
page_size | integer | peut être null | Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans |
total | integer · int64 | peut être null | Nombre de lignes correspondant au filtre. |
warnings | object[] | Explications de la liste ; vide si rien d'inhabituel. Codes fermés : | |
code | string | —filtered_by_key_package_filtersfiltered_by_provider_scopeno_price_for_orgprice_filter_removed_rowsprovider_disabledcapability_missing | |
count | integer · int64 | peut être null | — |
message | string | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedwebhook.not_configuredpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}/v1/webhooks/deliveries/{id}/replayRenvoyer un webhook
webhook:managepost_webhooks_deliveries__id__replayAutorisation requise: webhook:manage.
Paramètres du chemin (1)
idstringobligatoireID d’un envoi webhook (items[].id de GET /v1/webhooks/deliveries).
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
attempts | integer | — | |
created_at | string · date-time | — | |
event_id | string · uuid | Identifiant de l’ÉVÉNEMENT. Un rejeu crée un NOUVEL envoi avec le MÊME | |
event_type | string | — | |
id | string · uuid | — | |
last_attempt_at | string · date-time | peut être null | — |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
next_retry_at | string · date-time | peut être null | — |
response_code | integer | peut être null | — |
status | string | —pendingdeliveredfailed | |
webhook_id | string · uuid | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedwebhook.delivery_not_foundwebhook.not_configuredpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}Mode test
Utilisez uniquement les clés test tke_test_ ou tke_member_test_ sur api-testing. Simulez les données et eSIM du propriétaire sans fonds ni fournisseurs réels. Le mode test membre ne prend pas en charge webhooks, commandes groupées ou demandes de remboursement à valider.
/v1/testmode/esims/{id}/simulateSimuler le cycle de vie d’une eSIM de test (clés de test uniquement)
esims:writepost_testmode_esims__id__simulateAutorisation requise: esims:write.
Clés test tke_test_ ou tke_member_test_ uniquement. Appliquez un scénario à l’eSIM test du propriétaire : consommation, installation, expiration, débit limité ou rejet simulé de la prochaine opération. Le corps doit contenir au moins un champ ; sinon 422 testmode.invalid_scenario. Consultez le résultat par GET /v1/esims/{id} et /usage comme en mode réel. Les politiques de changement de forfait et d’annulation sont communes, sans appel au fournisseur réel.
Paramètres du chemin (1)
idstringobligatoireIdentifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.
Corps de la requêteobligatoireapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data_used_mb | integer · int64 | Données déjà utilisées en Mo (1 MiB binaire). Une valeur dépassant le quota est plafonnée : c'est le scénario d'épuisement, pas une erreur. | |
expire_at | string · date-time | Définir une date d’expiration précise (ISO-8601 UTC). | |
expire_now | boolean |
| |
provider_reject | boolean | Simuler un refus fournisseur à la prochaine opération (recharge ou annulation). | |
status | string | Cycle de vie TECHNIQUE du profil eSIM, selon le vocabulaire du fournisseur. Distinct du cycle commercial releasedinstalledenableddisabled | |
throttled | boolean |
|
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
cancellable | object | Possibilité d'annuler l'eSIM et motif du refus ; à lire avant | |
allowed | boolean | — | |
disclosed_at | string · date-time | peut être null | Moment où le code a quitté nos systèmes (ISO-8601 UTC), |
reason | string | peut être null | Motif du refus d'annulation : code_disclosedwrong_statusexpirednot_supportednull |
external_ref | string | peut être null | Votre référence de commande ( |
iccid | string | peut être null | — |
id | string · uuid | ID de l’eSIM, utilisé dans tous les chemins | |
install | object | peut être null | Toutes les données nécessaires à l’INSTALLATION de cette eSIM. Une réponse contenant ce bloc porte toujours |
android_url | string | peut être null | — |
apn | string | peut être null | Nom du point d’accès (APN) à saisir si la configuration automatique échoue. Toujours |
ios_url | string | peut être null | — |
lpa | string | peut être null | Code d’activation de l’eSIM, secret commercialisable. Cette chaîne suffit pour générer votre QR code. |
matching_id | string | peut être null | — |
qr_code_base64 | string | peut être null | QR code en data-URI PNG. Présent seulement avec |
smdp_address | string | peut être null | — |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
msisdn | string | peut être null | — |
order_id | string · uuid | — | |
package_code | string | peut être null | CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. |
package_name | string | NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement. | |
plan | object | peut être null | Le forfait actuellement appliqué à cette eSIM. |
current_code | string | — | |
expires_at | string · date-time | peut être null | — |
started_at | string · date-time | peut être null | — |
simulated | object | État simulé après application du scénario. | |
esim_state | string | Cycle TECHNIQUE de l’eSIM de test. En production il provient de releasedinstalledenableddisabled | |
last_via | string | Flux ayant MODIFIÉ cette eSIM le plus récemment. open_apisimulateseed | |
via | string | Flux ayant CRÉÉ cette eSIM de test. open_apisimulateseed | |
smdp_status | string | peut être null | — |
status | string | — | |
updated_at | string · date-time | — | |
usage | object | peut être null | Statistiques d'utilisation des données de l'eSIM. |
expires_at | string · date-time | peut être null | — |
remaining_bytes | integer · int64 | peut être null | — |
source | string | peut être null | —pollwebhook |
total_bytes | integer · int64 | peut être null | — |
updated_at | string · date-time | peut être null | — |
used_bytes | integer · int64 | peut être null | — |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbiddeninternal.not_foundtestmode.invalid_scenariopartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"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
}Exemple de corps
{
"data_used_mb": 4096,
"expire_at": "2026-07-11T08:12:30Z",
"expire_now": true,
"provider_reject": true,
"status": "enabled",
"throttled": true
}/v1/testmode/resetRéinitialiser les données de test (clés de test uniquement)
orders:writepost_testmode_resetAutorisation requise: orders:write.
Headers (1)
Idempotency-KeystringfacultatifCorps de la requêtefacultatifapplication/json
| Champ | Type | Contraintes | Description |
|---|---|---|---|
reason | string | — |
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
esim_states_deleted | integer · int64 | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
order_items_deleted | integer · int64 | — | |
orders_deleted | integer · int64 | — | |
wallet_balance | integer · int64 | Solde du portefeuille test APRÈS réinitialisation, toujours 0. Le grand livre n’est PAS supprimé : une écriture compensatrice remet le solde à zéro. | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbiddenpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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"Exemple de réponse
{
"data": {
"esim_states_deleted": 1,
"mode": "live",
"order_items_deleted": 1,
"orders_deleted": 1,
"wallet_balance": 1
},
"ok": true
}Exemple de corps
{
"reason": "Reset test data"
}/v1/testmode/seedCréer les données d’exemple du mode test (clés de test uniquement)
orders:writepost_testmode_seedAutorisation requise: orders:write.
Headers (1)
Idempotency-KeystringfacultatifRéponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
already_seeded | boolean | — | |
esims_created | integer | — | |
mode | string | Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé. testlive | |
orders_created | integer | — | |
seeded | boolean | — | |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbiddentestmode.no_sellable_packagepartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-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)"Exemple de réponse
{
"data": {
"already_seeded": true,
"esims_created": 1,
"mode": "live",
"orders_created": 1,
"seeded": true
},
"ok": true
}/v1/testmode/statusVue d’ensemble du mode test (clés de test uniquement)
esims:readget_testmode_statusAutorisation requise: esims:read.
Réponse
Succès
| Champ | Type | Contraintes | Description |
|---|---|---|---|
data | object | obligatoire | — |
base_url_testing | string | Hôte du mode test. Utilisez uniquement des clés tke_test_ ou tke_member_test_ ; un appel à l’hôte réel renvoie 403 partner.wrong_host. | |
capabilities | object | — | |
bulk_orders | boolean | — | |
cancel_refund | boolean | — | |
refund_requests | boolean | — | |
webhooks | boolean | — | |
checklist | object[] | Les étapes sont calculées à partir des appels API réels du même propriétaire en mode test. Les webhooks concernent uniquement les organisations. | |
done | boolean | — | |
label | string | — | |
last_at | string · date-time | peut être null | — |
step | string | — | |
drift | object | Boucle de consommation simulée : activation, intervalle et dernière exécution. Les webhooks de seuil concernent uniquement les organisations compatibles. | |
enabled | boolean | — | |
interval_secs | integer · int64 | — | |
last_run_at | string · date-time | peut être null | — |
mode | string | —testlive | |
ready | boolean | Indique si le propriétaire de la clé a terminé toutes les étapes prises en charge. Les membres ne doivent pas tester les webhooks. | |
sample_counts | object | — | |
test_esims | integer · int64 | — | |
test_orders | integer · int64 | — | |
seeded | boolean | Indique si le propriétaire de la clé dispose déjà de commandes et d’eSIM de test. | |
test_key_count | integer · int64 | — | |
wallet_balance | integer · int64 | Solde du portefeuille test en VND virtuels du propriétaire de la clé, séparé du portefeuille réel. | |
webhook | object | — | |
endpoint_url | string | peut être null | — |
fail_next | integer | — | |
status | string | peut être null | — |
ok | boolean | obligatoire | —true |
Réponses d’erreur
Toutes les erreurs retournent { ok: false, error } avec le bon statut HTTP, jamais 200. Détails sur la page des erreurs.
internal.validation_failedpartner.invalid_keypartner.invalid_signaturepartner.timestamp_expiredpartner.nonce_reusedpartner.ip_not_allowedpartner.scope_deniedpartner.key_suspendedpartner.key_revokedorg.suspendedtestmode.live_key_forbiddenpartner.rate_limitedpartner.endpoint_disabledinternal.service_unavailableExemple d’appel
Toutes les valeurs TKE-VN-30D-X3K9F sont à remplacer. Obtenez les codes réels via GET /v1/catalog/packages.
#!/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: fr" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG"Exemple de réponse
{
"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
}