Codes d’erreur
Les erreurs utilisent { ok: false, error: { code, message, field?, meta?, request_id } } avec un statut HTTP adapté, jamais 200. La liste /v1 et embed suit le registre backend.
Structure d’une erreur
http
HTTP/1.1 402 Payment Required
Content-Type: application/json
X-Request-Id: 018f2b00-0000-7000-8000-0000000000ff
{
"ok": false,
"error": {
"code": "wallet.insufficient_balance",
"message": "Số dư ví không đủ để thực hiện giao dịch.",
"field": null,
"meta": null,
"request_id": "018f2b00-0000-7000-8000-0000000000ff"
}
}Pourquoi domain.reason plutôt que 0000
HTTP natif : Les proxys, outils de supervision et clients REST interprètent déjà les statuts HTTP standard.
Explicite : wallet.insufficient_balance se comprend sans tableau ; de nouveaux codes ne cassent pas le traitement existant.
Le système attend un nombre ? Le code immuable sert de clé de traduction stable.
4 bonnes pratiques de gestion des erreurs
Statut HTTP d’abord, code ensuite
Le statut guide les tentatives, alertes et coupe-circuits ; error.code guide le message utilisateur. Les statuts sont plus stables.
Un code inconnu ne doit pas interrompre le flux
Sur code inconnu, utilisez le statut HTTP, journalisez code et request_id et affichez un message de repli. Évitez les exceptions non gérées.
Toujours journaliser request_id
X-Request-Id figure dans chaque réponse et dans l’erreur. Transmettez-le au support pour retrouver la requête.
Respecter Retry-After sur 429 et 503
Lisez Retry-After au lieu de fixer un délai. 429 fournit aussi X-RateLimit-Limit / Remaining / Reset.
Table code ↔ HTTP
Classement par domaine. Consultez aussi les erreurs propres à chaque endpoint dans la référence API.
91 / 91 codes affichés
Authentification, clés et limites partner.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
partner.invalid_key | 401 | Clé API invalide. | Non : elle échouerait encore |
partner.invalid_signature | 401 | Signature de requête invalide. | Non : elle échouerait encore |
partner.timestamp_expired | 401 | L'horodatage de la requête est trop éloigné de l'heure du serveur. | Non : elle échouerait encore |
partner.nonce_reused | 401 | Ce nonce a déjà été utilisé. | Non : elle échouerait encore |
partner.ip_not_allowed | 403 | Cette adresse IP ne figure pas dans la liste autorisée. | Non : elle échouerait encore |
partner.scope_denied | 403 | Cette clé API n'a pas le droit d'appeler cet endpoint. | Non : elle échouerait encore |
partner.key_suspended | 403 | Cette clé API est suspendue. | Non : elle échouerait encore |
partner.key_revoked | 403 | Cette clé API a été révoquée. | Non : elle échouerait encore |
partner.endpoint_disabled | 503 | Ce groupe d'endpoints est temporairement hors service. | Oui : requête identique |
partner.rate_limited | 429 | La limite de requêtes est dépassée. Réessayez plus tard. | Oui : requête identique |
partner.package_not_visible | 404 | Ce code de forfait ne peut pas être acheté avec votre clé. | Non : elle échouerait encore |
partner.daily_quota_exceeded | 429 | Le quota quotidien de requêtes est épuisé. Réessayez demain. | Oui : requête identique |
partner.wrong_host | 403 | Cette clé ne correspond pas à cet hôte. Utilisez `https://api-testing.tongkhoesim.com` pour `tke_test_` et `https://api.tongkhoesim.com` pour `tke_live_`. | Non : elle échouerait encore |
Erreurs générales de l’API internal.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
internal.validation_failed | 400 | Les données envoyées sont invalides. | Non : elle échouerait encore |
internal.invalid_filter | 400 | Condition de filtre invalide. | Non : elle échouerait encore |
internal.invalid_sort_field | 400 | Champ de tri invalide. | Non : elle échouerait encore |
internal.unauthenticated | 401 | Connectez-vous à nouveau : la session est absente ou expirée. | Non : elle échouerait encore |
internal.forbidden | 403 | Vous n'avez pas le droit d'effectuer cette action. | Non : elle échouerait encore |
internal.not_found | 404 | La ressource demandée est introuvable. | Non : elle échouerait encore |
internal.route_not_found | 404 | Ce chemin n'existe pas. Vérifiez la méthode et le chemin dans la documentation. | Non : elle échouerait encore |
internal.method_not_allowed | 405 | Cette méthode HTTP n'est pas prise en charge. | Non : elle échouerait encore |
internal.conflict | 409 | Un conflit d'état empêche cette action. | Non : elle échouerait encore |
internal.unsupported_media_type | 415 | Ce type de contenu n'est pas pris en charge. | Non : elle échouerait encore |
internal.rate_limited | 429 | Trop de requêtes. Réessayez plus tard. | Oui : requête identique |
internal.server_error | 500 | Une erreur système est survenue. Réessayez plus tard. | Oui : requête identique |
internal.service_unavailable | 503 | Le service est temporairement indisponible. Réessayez plus tard. | Oui : requête identique |
internal.limit_exceeded | 422 | La limite autorisée est atteinte. Supprimez des éléments et réessayez. | Non : elle échouerait encore |
internal.money_frozen | 503 | Les opérations financières sont suspendues pour vérifier le grand livre. Réessayez plus tard. | Oui : requête identique |
Catalogue des forfaits catalog.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
catalog.cursor_mismatch | 400 | Ce curseur ne correspond pas aux filtres actuels. Repartez de la première page sans le paramètre cursor. | Non : elle échouerait encore |
Devis et commandes order.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
order.quote_not_found | 404 | Devis introuvable. | Non : elle échouerait encore |
order.quote_expired | 409 | Ce devis a expiré. Créez-en un nouveau. | Non : elle échouerait encore |
order.quote_already_committed | 409 | Ce devis a déjà été converti en commande. | Non : elle échouerait encore |
order.package_not_allowed | 400 | Ce forfait n'existe pas ou n'est pas disponible à l'achat. | Non : elle échouerait encore |
order.not_deliverable | 409 | Cette commande n'est pas encore terminée ; cette action est indisponible. | Non : elle échouerait encore |
order.quantity_limit_exceeded | 400 | Le nombre d'eSIM dépasse la limite par commande. | Non : elle échouerait encore |
order.fulfillment_config_changed | 409 | La configuration de livraison du forfait a changé. Créez un nouveau devis. | Non : elle échouerait encore |
order.refund_suggestion_pending | 409 | Une proposition de remboursement attend une approbation pour cette commande. Traitez-la d'abord. | Non : elle échouerait encore |
eSIM esim.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
esim.out_of_stock | 409 | Le stock de ce forfait est insuffisant. | Non : elle échouerait encore |
esim.not_cancellable | 409 | L'état de cette eSIM ne permet pas son annulation. | Non : elle échouerait encore |
esim.lpa_not_available | 409 | Cette eSIM n'a pas encore de code LPA (pas encore activée ou enregistrée). | Non : elle échouerait encore |
esim.not_eligible | 409 | Cette eSIM n'est pas admissible à une recharge. | Non : elle échouerait encore |
esim.not_activated | 409 | Cette eSIM n'est pas installée. Installez-la et activez-la avant de la recharger. | Non : elle échouerait encore |
esim.topup_not_supported | 409 | Ce forfait ne permet pas les recharges. | Non : elle échouerait encore |
esim.topup_provider_mismatch | 409 | Ce forfait de recharge appartient à un autre fournisseur. Choisissez un forfait proposé pour cette eSIM. | Non : elle échouerait encore |
esim.refund_not_eligible | 409 | Cette eSIM n'est pas admissible à une demande de remboursement. | Non : elle échouerait encore |
esim.refund_request_pending | 409 | Une demande de remboursement est déjà en attente pour cette eSIM. | Non : elle échouerait encore |
esim.suspended | 409 | Cette eSIM est suspendue. Réactivez-la avant cette action. | Non : elle échouerait encore |
Portefeuille wallet.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
wallet.frozen | 409 | Ce portefeuille est gelé et ne permet aucun paiement. | Non : elle échouerait encore |
wallet.velocity_hold | 409 | Ce portefeuille est bloqué pour vérification. Contactez l'assistance. | Non : elle échouerait encore |
wallet.insufficient_balance | 402 | Le solde du portefeuille est insuffisant pour cette transaction. | Non : elle échouerait encore |
wallet.deleted | 409 | Le portefeuille destinataire est invalide (supprimé). | Non : elle échouerait encore |
wallet.velocity_exceeded | 429 | Le seuil de transactions a été dépassé sur une courte période. Le portefeuille est bloqué pour vérification. | Non : elle échouerait encore |
Demandes de remboursement refund.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
refund.note_required | 400 | Indiquez le motif du refus de remboursement. | Non : elle échouerait encore |
Prix de vente pricing.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
pricing.no_price_for_buyer | 409 | Aucun prix de vente n'est encore défini pour vous sur ce forfait. | Non : elle échouerait encore |
Fournisseur eSIM provider.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
provider.disabled | 409 | Le fournisseur de ce forfait est désactivé ; la vente est impossible. | Non : elle échouerait encore |
provider.api_group_disabled | 409 | Ce groupe de services du fournisseur est désactivé. | Non : elle échouerait encore |
provider.not_registered | 500 | Aucun connecteur n'est configuré pour ce fournisseur. | Oui : requête identique |
provider.unsupported | 400 | Ce fournisseur ne prend pas en charge cette action. | Non : elle échouerait encore |
provider.timeout | 504 | Le fournisseur a mis trop longtemps à répondre. Réessayez plus tard. | Oui : requête identique |
provider.auth_failed | 502 | L'authentification auprès du fournisseur a échoué. | Oui : requête identique |
provider.rate_limited | 429 | Le fournisseur limite les requêtes. Réessayez plus tard. | Oui : requête identique |
provider.malformed_response | 502 | Le fournisseur a renvoyé des données invalides. | Oui : requête identique |
provider.call_failed | 502 | L'appel au fournisseur a échoué. | Oui : requête identique |
provider.out_of_stock | 409 | Ce forfait est en rupture de stock chez le fournisseur. | Non : elle échouerait encore |
provider.msisdn_taken | 409 | Ce MSISDN appartient déjà à un autre abonné chez le fournisseur. | Non : elle échouerait encore |
provider.msisdn_exhausted | 409 | Aucun MSISDN utilisable ne reste chez le fournisseur. | Non : elle échouerait encore |
Idempotency idempotency.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
idempotency.conflict | 409 | Cette Idempotency-Key a déjà été utilisée avec un contenu différent. | Non : elle échouerait encore |
Capacités du fournisseur capability.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
capability.not_supported | 422 | Ce forfait ne prend pas en charge la fonctionnalité demandée. | Non : elle échouerait encore |
Mode test / simulation testmode.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
testmode.live_key_forbidden | 403 | Cet endpoint exige une clé de test (`tke_test_`) sur api-testing. Remplacez votre clé réelle par une clé de test de la même organisation. | Non : elle échouerait encore |
testmode.not_test_item | 422 | Cette eSIM est réelle ; le mode test ne traite que les eSIM de test. Pour annuler une commande réelle, demandez un remboursement. | Non : elle échouerait encore |
testmode.invalid_scenario | 422 | Scénario invalide. Envoyez au moins un champ parmi `data_used_mb`, `status`, `expire_now`, `throttled` ; `status` accepte uniquement released|installed|enabled|disabled. | Non : elle échouerait encore |
testmode.no_sellable_package | 422 | Aucun forfait tarifé ne permet de créer les données d'exemple. Il faut au moins un forfait disponible avec un prix. | Non : elle échouerait encore |
Webhook webhook.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
webhook.health_check_failed | 422 | L'URL du webhook n'a pas répondu correctement au contrôle de disponibilité. | Non : elle échouerait encore |
webhook.url_not_allowed | 422 | Cette URL de webhook n'est pas autorisée. Elle doit être publique et utiliser https. | Non : elle échouerait encore |
webhook.not_configured | 404 | Aucun webhook n'est configuré pour cette clé API. | Non : elle échouerait encore |
webhook.delivery_not_found | 404 | Envoi de webhook introuvable. | Non : elle échouerait encore |
webhook.already_configured | 409 | Cette clé API a déjà un webhook (un seul par clé). Modifiez-le avec `PUT /v1/webhooks/{id}`, ou supprimez-le avec `DELETE /v1/webhooks/{id}` avant d'en créer un nouveau. | Non : elle échouerait encore |
webhook.not_found | 404 | Cet endpoint webhook n'appartient pas à la clé API appelante. | Non : elle échouerait encore |
Statut de l’organisation org.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
org.invoice_profile_incomplete | 422 | Le profil de facturation de l'organisation est incomplet (raison sociale, numéro fiscal, adresse). | Non : elle échouerait encore |
org.suspended | 403 | Cette organisation est suspendue. | Non : elle échouerait encore |
org.closed | 403 | Cette organisation est fermée. | Non : elle échouerait encore |
Intégration publique embed.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
embed.invalid_token | 401 | Jeton d'intégration invalide. | Non : elle échouerait encore |
embed.token_revoked | 410 | Ce canal d'intégration n'est plus actif. | Non : elle échouerait encore |
embed.rate_limited | 429 | Ce canal d'intégration appelle trop vite. Réessayez plus tard. | Oui : requête identique |
embed.package_not_visible | 404 | Forfait introuvable. | Non : elle échouerait encore |
Factures de TVA invoice.*
| code | HTTP | Signification | Nouvelle tentative ? |
|---|---|---|---|
invoice.order_not_settled | 409 | La commande n'est pas soldée : un remboursement est en cours. Réessayez la facturation dans quelques minutes. | Oui : requête identique |
invoice.profile_incomplete | 422 | Le profil de facturation de l'organisation est incomplet. Complétez-le avant d'émettre une facture. | Non : elle échouerait encore |
invoice.already_requested | 409 | Cette commande possède déjà une facture. | Non : elle échouerait encore |
invoice.nothing_to_invoice | 409 | Après déduction des remboursements, aucun montant ne reste à facturer. | Non : elle échouerait encore |
invoice.amount_changed | 409 | Un remboursement vient de modifier le montant de la commande. Vérifiez-le puis relancez la facturation. | Oui : requête identique |
invoice.buyer_invalid | 422 | Les informations de l'entreprise destinataire sont incomplètes ou le numéro fiscal est invalide. | Non : elle échouerait encore |