Documentation API
Accès programmatique aux numéros virtuels, commandes et solde du compte.
Apercu
Tous les champs monétaires de l'API /v1 sont en IDR (roupie indonésienne), sous forme d'unités entières — par exemple, "price": 15000 et "balance": 500000 signifient Rp 15 000 et Rp 500 000. Pour une projection native en USD du même registre, passez à l'API v2 à l'aide du sélecteur de version ci-dessus.
⟩Authentification
Toutes les requêtes API nécessitent un Bearer token. Générez-en un depuis les Paramètres du compte dans le tableau de bord, puis incluez-le dans chaque requête :
Les requêtes sans token valide reçoivent une réponse 401 UNAUTHORIZED.
⟩URL de base
Tous les chemins d'endpoints ci-dessous sont relatifs à :
⟩Format de réponse
Chaque réponse renvoie du JSON avec une enveloppe cohérente. Toutes les réponses incluent un en-tête x-request-id pour le débogage.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Tous les champs monétaires de l'API /v1 sont en IDR (roupie indonésienne), sous forme d'unités entières — par exemple, "price": 15000 et "balance": 500000 signifient Rp 15 000 et Rp 500 000. Pour une projection native en USD du même registre, passez à l'API v2 à l'aide du sélecteur de version ci-dessus.
/catalog/countries Renvoie la liste de tous les pays disponibles.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v1/catalog/countries \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/catalog/countries", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/countries",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} /catalog/services Renvoie la liste des services (plateformes) disponibles. Filtrage optionnel par pays.
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
country_id | integer | Non | Filtrer les services disponibles pour ce pays |
Exemple de requête
curl -s "https://api.smscode.gg/v1/catalog/services?country_id=6" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=6", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/services",
params={"country_id": 6},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} /catalog/products Renvoie une liste paginée des produits disponibles. Filtrage par pays et/ou plateforme.
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
country_id | integer | Non | Filtrer par identifiant de pays |
platform_id | integer | Non | Filtrer par identifiant de plateforme/service |
sort | string | Non | Ordre de tri : price_asc (défaut), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Non | Résultats par page (1-10 000, défaut 1 000) |
page | integer | Non | Numéro de page (min. 1, défaut 1) |
Exemple de requête
curl -s "https://api.smscode.gg/v1/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/products?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/products",
params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": 15000,
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1 }
} /catalog/exchange-rate Renvoie le taux de change USD/IDR actuel utilisé pour la conversion de devises.
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
pair | string | Non | Paire de devises (défaut : USD/IDR) |
Exemple de requête
curl -s "https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/exchange-rate",
params={"pair": "USD/IDR"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"pair": "USD/IDR",
"base_currency": "USD",
"quote_currency": "IDR",
"rate": 16250
}
} /balance Renvoie le solde du compte de l'utilisateur authentifié.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v1/balance \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/balance", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/balance",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
} /orders Renvoie la liste des commandes de l'utilisateur authentifié, triées par date décroissante. Filtrage par statut et pagination via offset.
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
limit | integer | Non | Nombre max. de résultats (1-100, défaut 20) |
offset | integer | Non | Nombre de résultats à ignorer (défaut 0) |
status | string | Non | Filtrer par statut : ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (insensible à la casse) |
Exemple de requête
curl -s "https://api.smscode.gg/v1/orders?limit=5&status=ACTIVE" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v1/orders?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/orders",
params={"limit": 5, "status": "ACTIVE", "offset": 0},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 1001,
"status": "ACTIVE",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": 15000,
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
}
]
} /orders/{id} Renvoie une commande par son identifiant. Ne renvoie que les commandes de l'utilisateur authentifié.
Paramètres de chemin
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de commande (paramètre de chemin) |
Exemple de requête
curl -s https://api.smscode.gg/v1/orders/1001 \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/orders/1001", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/orders/1001",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"id": 1001,
"status": "OTP_RECEIVED",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": 15000,
"otp_code": "123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
}
} /orders/active Liste toutes les commandes actuellement actives (ACTIVE + OTP_RECEIVED). Utilisez cet endpoint pour surveiller les mises à jour de statut OTP.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v1/orders/active \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/orders/active", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/orders/active",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 1001,
"status": "OTP_RECEIVED",
"otp_code": "123456",
"otp_message": "Your verification code is 123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"failed_reason": null
},
{
"id": 1002,
"status": "ACTIVE",
"otp_code": null,
"otp_message": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
]
} /orders/create Crée une nouvelle commande de numéro virtuel. Débite le solde automatiquement. Prend en charge un en-tête Idempotency-Key optionnel pour éviter les commandes en double lors des nouvelles tentatives réseau.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
product_id | integer | Non | Identifiant du produit à commander. Fournissez UN SEUL des deux : product_id OU catalog_product_id, pas les deux. |
catalog_product_id | integer | Non | Identifiant de produit stable — le serveur choisit la meilleure offre actuelle (la moins chère, en tenant compte de la fiabilité). Fournissez celui-ci ou product_id. |
max_price | integer · string | Non | Plafond de prix facultatif. v1 : entier en IDR. v2 : chaîne décimale en USD (ex. "0.50"). |
prefer_provider | string | Non | Code de fournisseur facultatif à privilégier en cas d'offres équivalentes. |
policy | string | Non | Politique de routage facultative, valable uniquement avec catalog_product_id. Valeurs : cheapest (par défaut) choisit l'offre saine la moins chère ; best_success classe les offres d'abord selon le succès de livraison récent. best_success note chaque fournisseur sur la proportion de commandes ayant reçu un OTP au cours des 30 derniers jours complets, par tranches de 10%, et ne le comptabilise qu'à partir d'au moins 20 commandes sur cette période — les fournisseurs sous ce seuil ou sans historique sont considérés comme neutres, de sorte que les nouvelles offres ne sont jamais écartées (sur option ; le signal démarre neutre). Si prefer_provider est également défini, le fournisseur préféré reste en première position. |
quantity | integer | Non | Quantité (1-100, défaut 1) |
Passez un en-tête Idempotency-Key pour réessayer en toute sécurité sans créer de doublons. La clé peut contenir des lettres, des chiffres, un trait d'union et un tiret bas (A-Z a-z 0-9 _ -), jusqu'à 128 caractères ; une clé invalide est rejetée avec 422 VALIDATION_ERROR. Réessayer avec la même clé et le même corps rejoue le résultat d'origine (y compris le failed_count d'un succès partiel). Une nouvelle tentative qui atteint le fournisseur mais échoue est enregistrée et rejoue cette même erreur — utilisez une NOUVELLE clé pour réessayer. Les échecs sans effet de bord (solde insuffisant, aucune offre disponible) libèrent la clé, vous pouvez donc recharger votre solde et réessayer avec la même clé. Réutiliser une clé avec un corps différent renvoie 422 IDEMPOTENCY_KEY_REUSED, et une requête encore en cours avec cette clé renvoie 409 REQUEST_IN_PROGRESS. Le champ failed_reason dans les réponses de create est toujours null — il n'est renseigné qu'à la consultation/au listage des commandes.
Exemple de requête
curl -s -X POST https://api.smscode.gg/v1/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-123" \
-d '{"product_id":142,"quantity":1}'
# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v1/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-124" \
-d '{"catalog_product_id":87,"max_price":20000}'const res = await fetch("https://api.smscode.gg/v1/orders/create", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "unique-request-id-123",
},
body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/create",
json={"product_id": 142, "quantity": 1},
headers={
"Authorization": "Bearer YOUR_API_TOKEN",
"Idempotency-Key": "unique-request-id-123",
})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"orders": [
{
"id": 1002,
"status": "ACTIVE",
"product_id": 142,
"catalog_product_id": 87,
"amount": 15000,
"phone_number": "+6281234567891",
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
],
"failed_count": 0
}
} /orders/cancel Annule une commande active. Le coût de la location est remboursé sur le solde de votre compte.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de la commande à annuler |
Exemple de requête
curl -s -X POST https://api.smscode.gg/v1/orders/cancel \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v1/orders/cancel", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/cancel",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
} /orders/finish Marque une commande comme terminée après réception de l'OTP. Cela libère le numéro immédiatement au lieu d'attendre l'expiration.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de la commande à finaliser |
Exemple de requête
curl -s -X POST https://api.smscode.gg/v1/orders/finish \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v1/orders/finish", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/finish",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} /orders/resend Demande à la plateforme de renvoyer le SMS au numéro loué. Toutes les plateformes ne prennent pas en charge le renvoi — vérifiez le champ resent dans la réponse.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de la commande pour le renvoi SMS |
Exemple de requête
curl -s -X POST https://api.smscode.gg/v1/orders/resend \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v1/orders/resend", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/resend",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} /webhook Renvoie votre configuration actuelle de notifications webhook.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v1/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/webhook", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/webhook",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook Met à jour votre URL webhook et/ou votre secret. Un secret est généré automatiquement lorsque vous définissez une URL pour la première fois. Envoyez une chaîne vide pour effacer. L'URL doit utiliser HTTPS.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
webhook_url | string | Non | URL HTTPS pour recevoir les événements webhook (chaîne vide pour effacer) |
webhook_secret | string | Non | Secret partagé pour la signature HMAC-SHA256 (généré automatiquement s'il est omis lors de la première configuration) |
Au moins un champ est requis.
Exemple de requête
curl -s -X PATCH https://api.smscode.gg/v1/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://example.com/webhook"}'const res = await fetch("https://api.smscode.gg/v1/webhook", {
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();import requests
res = requests.patch("https://api.smscode.gg/v1/webhook",
json={"webhook_url": "https://example.com/webhook"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook/test Envoie un événement test à votre URL webhook configurée. Renvoie le code de statut HTTP de votre serveur. Utile pour vérifier que votre endpoint fonctionne avant la mise en production.
Paramètres
Aucun
Exemple de requête
curl -s -X POST https://api.smscode.gg/v1/webhook/test \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/webhook/test", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/webhook/test",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"status_code": 200
}
} ⟩Notifications webhook
Configurez une URL webhook pour recevoir des notifications push en temps réel pour les événements de commande au lieu du polling. C'est l'approche recommandée pour les scripts de bot.
Événements
| Événement | Déclencheur |
|---|---|
order.otp_received | Code OTP livré au numéro loué |
order.completed | Commande marquée comme terminée (manuellement ou par expiration) |
order.expired | Commande expirée sans OTP (solde remboursé) |
order.canceled | Commande annulée par l'utilisateur (solde remboursé) |
Payload
{
"event": "order.otp_received",
"timestamp": "2026-02-25T12:00:00+00:00",
"data": {
"order_id": 1001,
"phone_number": "+628123456789",
"otp_code": "1234",
"otp_message": "Your verification code is 1234",
"product_id": 42,
"catalog_product_id": 87,
"country": "Indonesia",
"platform": "WhatsApp"
}
} Vérification de la signature
Chaque requête webhook inclut un en-tête X-Webhook-Signature avec une signature HMAC-SHA256 du corps de la requête, utilisant votre webhook_secret comme clé :
Vérifiez cette signature côté serveur pour vous assurer que la requête est authentique. La livraison est de type « fire-and-forget » avec un délai d'expiration de 3 secondes et sans nouvelle tentative.
⟩Limites de débit
Les requêtes API sont soumises à des limites de débit par groupe d'endpoints. Le dépassement de la limite renvoie un code 429 Too Many Requests avec un en-tête Retry-After indiquant le nombre de secondes à attendre.
| Groupe d'endpoints | Limite | Fenêtre |
|---|---|---|
| Catalogue (pays, services, produits, taux de change) | 5 000 requêtes | 60 secondes |
| Solde | 600 requêtes | 60 secondes |
| Lecture de commandes (liste, détail, actives) | 5 000 requêtes | 60 secondes |
| Création de commande | 3 000 requêtes | 60 secondes |
| Annulation de commande | 1 000 requêtes | 60 secondes |
| Actions sur les commandes (finaliser, renvoyer) | 1 000 requêtes | 60 secondes |
| Configuration webhook (lecture, mise à jour) | 600 requêtes | 60 secondes |
| Test webhook | 10 requêtes | 60 secondes |
⟩Codes d'erreur
Les réponses d'erreur incluent l'un de ces codes dans error.code :
| Code | HTTP | Description |
|---|---|---|
UNAUTHORIZED | 401 | Token API manquant ou invalide |
FORBIDDEN | 403 | Accès refusé |
NOT_FOUND | 404 | Ressource introuvable (commande, taux de change, etc.) |
CONFLICT | 409 | Requête en double ou conflit de ressource |
INSUFFICIENT_BALANCE | 409 | Solde insuffisant pour créer la commande |
VALIDATION_ERROR | 422 | Les paramètres de la requête n'ont pas passé la validation |
RATE_LIMIT_EXCEEDED | 429 | Trop de requêtes (vérifiez l'en-tête Retry-After) |
INTERNAL_ERROR | 500 | Erreur interne du serveur |
PROVIDER_ERROR | 422 | Le fournisseur SMS en amont a rejeté la requête. En cas d'échec de création de commande, l'erreur peut inclure <code>details</code> : <code>cause_counts</code> (commandes avec <code>product_id</code> hérité — un décompte regroupé par cause) ou <code>attempts</code> (commandes avec <code>catalog_product_id</code> — résultats par tentative), avec les valeurs <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>. |
NO_OFFER_AVAILABLE | 422 | Aucune offre active ne correspond au produit et à la politique demandés (plafond de prix, disponibilité). |
CANCEL_TOO_EARLY | 409 | Commande trop récente pour être annulée — patientez 2 minutes |
REQUEST_IN_PROGRESS | 409 | Une requête de création avec cette clé d'idempotence est encore en cours |
IDEMPOTENCY_KEY_REUSED | 422 | Cette clé d'idempotence a déjà été utilisée avec un corps de requête différent |
SERVICE_UNAVAILABLE | 503 | Service temporairement indisponible (maintenance) |
Apercu
Tous les champs monétaires de l'API /v2 sont en USD et renvoyés sous forme d'objet monétaire — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount est une chaîne décimale ; canonical_amount est la valeur IDR exacte du registre (utilisez-la pour le rapprochement). Le rate USD/IDR appliqué est communiqué une seule fois par réponse dans meta.fx. v2 est une projection en USD au moment du rendu, par-dessus le même registre IDR que v1 — elle ne stocke ni ne traite jamais d'USD.
⟩Authentification
Toutes les requêtes API nécessitent un Bearer token. Générez-en un depuis les Paramètres du compte dans le tableau de bord, puis incluez-le dans chaque requête :
Les requêtes sans token valide reçoivent une réponse 401 UNAUTHORIZED.
⟩URL de base
Tous les chemins d'endpoints ci-dessous sont relatifs à :
⟩Format de réponse
Chaque réponse renvoie du JSON avec une enveloppe cohérente. Toutes les réponses incluent un en-tête x-request-id pour le débogage.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Erreur v2 : aucun taux de change utilisable (503)
{
"success": false,
"error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
} Tous les champs monétaires de l'API /v2 sont en USD et renvoyés sous forme d'objet monétaire — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount est une chaîne décimale ; canonical_amount est la valeur IDR exacte du registre (utilisez-la pour le rapprochement). Le rate USD/IDR appliqué est communiqué une seule fois par réponse dans meta.fx. v2 est une projection en USD au moment du rendu, par-dessus le même registre IDR que v1 — elle ne stocke ni ne traite jamais d'USD.
/catalog/countries Renvoie la liste de tous les pays disponibles.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v2/catalog/countries \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/catalog/countries", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/countries",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} Identique à v1 — seul le chemin de base change (/v1 → /v2).
/catalog/services Renvoie la liste des services (plateformes) disponibles. Filtrage optionnel par pays.
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
country_id | integer | Non | Filtrer les services disponibles pour ce pays |
Exemple de requête
curl -s "https://api.smscode.gg/v2/catalog/services?country_id=6" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=6", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/services",
params={"country_id": 6},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} Identique à v1 — seul le chemin de base change (/v1 → /v2).
/catalog/products Renvoie une liste paginée des produits disponibles. Filtrage par pays et/ou plateforme.
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
country_id | integer | Non | Filtrer par identifiant de pays |
platform_id | integer | Non | Filtrer par identifiant de plateforme/service |
sort | string | Non | Ordre de tri : price_asc (défaut), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Non | Résultats par page (1-10 000, défaut 1 000) |
page | integer | Non | Numéro de page (min. 1, défaut 1) |
Exemple de requête
curl -s "https://api.smscode.gg/v2/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/products?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/products",
params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2 : les champs monétaires sont des objets monétaires en USD et la réponse contient un unique meta.fx { pair, rate, rate_as_of }. rate est le montant entier en IDR pour 1 USD, donc USD = canonical_amount / rate. Les totaux utilisent 2 décimales ; les prix/remboursements par article en utilisent 4. Un montant strictement positif n'est jamais arrondi à 0.00. rate_as_of est l'horodatage RFC3339 du taux (au format +00:00) ou null lorsqu'aucun horodatage n'est enregistré.
v2 uniquement : s'il n'existe aucun taux USD/IDR utilisable, les endpoints monétaires renvoient 503 FX_RATE_UNAVAILABLE avec un en-tête Retry-After au lieu d'un corps monétaire. v1 ne renvoie jamais cela.
/catalog/exchange-rate Renvoie le taux de change USD/IDR actuel utilisé pour la conversion de devises.
Paramètres
Aucun — v2 renvoie toujours USD/IDR ; le paramètre ?pair de v1 est ignoré.
Exemple de requête
curl -s "https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/exchange-rate",
params={"pair": "USD/IDR"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
} v2 : renvoie { pair, rate, rate_as_of } (pas de base_currency/quote_currency, pas d'enveloppe meta — le taux est la donnée). ?pair est ignoré — v2 renvoie toujours USD/IDR (v1 prend en compte ?pair). Renvoie 503 FX_RATE_UNAVAILABLE s'il n'existe aucun taux utilisable.
/balance Renvoie le solde du compte de l'utilisateur authentifié.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v2/balance \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/balance", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/balance",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2 : les champs monétaires sont des objets monétaires en USD et la réponse contient un unique meta.fx { pair, rate, rate_as_of }. rate est le montant entier en IDR pour 1 USD, donc USD = canonical_amount / rate. Les totaux utilisent 2 décimales ; les prix/remboursements par article en utilisent 4. Un montant strictement positif n'est jamais arrondi à 0.00. rate_as_of est l'horodatage RFC3339 du taux (au format +00:00) ou null lorsqu'aucun horodatage n'est enregistré.
v2 uniquement : s'il n'existe aucun taux USD/IDR utilisable, les endpoints monétaires renvoient 503 FX_RATE_UNAVAILABLE avec un en-tête Retry-After au lieu d'un corps monétaire. v1 ne renvoie jamais cela.
/orders Renvoie la liste des commandes de l'utilisateur authentifié, triées par date décroissante. Filtrage par statut et pagination via offset.
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
limit | integer | Non | Nombre max. de résultats (1-100, défaut 20) |
offset | integer | Non | Nombre de résultats à ignorer (défaut 0) |
status | string | Non | Filtrer par statut : ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (insensible à la casse) |
Exemple de requête
curl -s "https://api.smscode.gg/v2/orders?limit=5&status=ACTIVE" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v2/orders?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/orders",
params={"limit": 5, "status": "ACTIVE", "offset": 0},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 1001,
"status": "ACTIVE",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
}
],
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2 : les champs monétaires sont des objets monétaires en USD et la réponse contient un unique meta.fx { pair, rate, rate_as_of }. rate est le montant entier en IDR pour 1 USD, donc USD = canonical_amount / rate. Les totaux utilisent 2 décimales ; les prix/remboursements par article en utilisent 4. Un montant strictement positif n'est jamais arrondi à 0.00. rate_as_of est l'horodatage RFC3339 du taux (au format +00:00) ou null lorsqu'aucun horodatage n'est enregistré.
v2 uniquement : s'il n'existe aucun taux USD/IDR utilisable, les endpoints monétaires renvoient 503 FX_RATE_UNAVAILABLE avec un en-tête Retry-After au lieu d'un corps monétaire. v1 ne renvoie jamais cela.
/orders/{id} Renvoie une commande par son identifiant. Ne renvoie que les commandes de l'utilisateur authentifié.
Paramètres de chemin
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de commande (paramètre de chemin) |
Exemple de requête
curl -s https://api.smscode.gg/v2/orders/1001 \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/orders/1001", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/orders/1001",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"id": 1001,
"status": "OTP_RECEIVED",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"otp_code": "123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2 : les champs monétaires sont des objets monétaires en USD et la réponse contient un unique meta.fx { pair, rate, rate_as_of }. rate est le montant entier en IDR pour 1 USD, donc USD = canonical_amount / rate. Les totaux utilisent 2 décimales ; les prix/remboursements par article en utilisent 4. Un montant strictement positif n'est jamais arrondi à 0.00. rate_as_of est l'horodatage RFC3339 du taux (au format +00:00) ou null lorsqu'aucun horodatage n'est enregistré.
v2 uniquement : s'il n'existe aucun taux USD/IDR utilisable, les endpoints monétaires renvoient 503 FX_RATE_UNAVAILABLE avec un en-tête Retry-After au lieu d'un corps monétaire. v1 ne renvoie jamais cela.
/orders/active Liste toutes les commandes actuellement actives (ACTIVE + OTP_RECEIVED). Utilisez cet endpoint pour surveiller les mises à jour de statut OTP.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v2/orders/active \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/orders/active", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/orders/active",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": [
{
"id": 1001,
"status": "OTP_RECEIVED",
"otp_code": "123456",
"otp_message": "Your verification code is 123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"failed_reason": null
},
{
"id": 1002,
"status": "ACTIVE",
"otp_code": null,
"otp_message": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
]
} v2 : cet endpoint n'est pas porteur de montant — il ne renvoie ni amount ni meta.fx (même structure que v1, sous /v2).
/orders/create Crée une nouvelle commande de numéro virtuel. Débite le solde automatiquement. Prend en charge un en-tête Idempotency-Key optionnel pour éviter les commandes en double lors des nouvelles tentatives réseau.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
product_id | integer | Non | Identifiant du produit à commander. Fournissez UN SEUL des deux : product_id OU catalog_product_id, pas les deux. |
catalog_product_id | integer | Non | Identifiant de produit stable — le serveur choisit la meilleure offre actuelle (la moins chère, en tenant compte de la fiabilité). Fournissez celui-ci ou product_id. |
max_price | integer · string | Non | Plafond de prix facultatif. v1 : entier en IDR. v2 : chaîne décimale en USD (ex. "0.50"). |
prefer_provider | string | Non | Code de fournisseur facultatif à privilégier en cas d'offres équivalentes. |
policy | string | Non | Politique de routage facultative, valable uniquement avec catalog_product_id. Valeurs : cheapest (par défaut) choisit l'offre saine la moins chère ; best_success classe les offres d'abord selon le succès de livraison récent. best_success note chaque fournisseur sur la proportion de commandes ayant reçu un OTP au cours des 30 derniers jours complets, par tranches de 10%, et ne le comptabilise qu'à partir d'au moins 20 commandes sur cette période — les fournisseurs sous ce seuil ou sans historique sont considérés comme neutres, de sorte que les nouvelles offres ne sont jamais écartées (sur option ; le signal démarre neutre). Si prefer_provider est également défini, le fournisseur préféré reste en première position. |
quantity | integer | Non | Quantité (1-100, défaut 1) |
Passez un en-tête Idempotency-Key pour réessayer en toute sécurité sans créer de doublons. La clé peut contenir des lettres, des chiffres, un trait d'union et un tiret bas (A-Z a-z 0-9 _ -), jusqu'à 128 caractères ; une clé invalide est rejetée avec 422 VALIDATION_ERROR. Réessayer avec la même clé et le même corps rejoue le résultat d'origine (y compris le failed_count d'un succès partiel). Une nouvelle tentative qui atteint le fournisseur mais échoue est enregistrée et rejoue cette même erreur — utilisez une NOUVELLE clé pour réessayer. Les échecs sans effet de bord (solde insuffisant, aucune offre disponible) libèrent la clé, vous pouvez donc recharger votre solde et réessayer avec la même clé. Réutiliser une clé avec un corps différent renvoie 422 IDEMPOTENCY_KEY_REUSED, et une requête encore en cours avec cette clé renvoie 409 REQUEST_IN_PROGRESS. Le champ failed_reason dans les réponses de create est toujours null — il n'est renseigné qu'à la consultation/au listage des commandes.
Exemple de requête
curl -s -X POST https://api.smscode.gg/v2/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-123" \
-d '{"product_id":142,"quantity":1}'
# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v2/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-124" \
-d '{"catalog_product_id":87,"max_price":20000}'const res = await fetch("https://api.smscode.gg/v2/orders/create", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "unique-request-id-123",
},
body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/create",
json={"product_id": 142, "quantity": 1},
headers={
"Authorization": "Bearer YOUR_API_TOKEN",
"Idempotency-Key": "unique-request-id-123",
})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"orders": [
{
"id": 1002,
"status": "ACTIVE",
"product_id": 142,
"catalog_product_id": 87,
"amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"phone_number": "+6281234567891",
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
],
"failed_count": 0
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2 : les champs monétaires sont des objets monétaires en USD et la réponse contient un unique meta.fx { pair, rate, rate_as_of }. rate est le montant entier en IDR pour 1 USD, donc USD = canonical_amount / rate. Les totaux utilisent 2 décimales ; les prix/remboursements par article en utilisent 4. Un montant strictement positif n'est jamais arrondi à 0.00. rate_as_of est l'horodatage RFC3339 du taux (au format +00:00) ou null lorsqu'aucun horodatage n'est enregistré.
v2 uniquement : s'il n'existe aucun taux USD/IDR utilisable, les endpoints monétaires renvoient 503 FX_RATE_UNAVAILABLE avec un en-tête Retry-After au lieu d'un corps monétaire. v1 ne renvoie jamais cela.
/orders/cancel Annule une commande active. Le coût de la location est remboursé sur le solde de votre compte.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de la commande à annuler |
Exemple de requête
curl -s -X POST https://api.smscode.gg/v2/orders/cancel \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v2/orders/cancel", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/cancel",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2 : les champs monétaires sont des objets monétaires en USD et la réponse contient un unique meta.fx { pair, rate, rate_as_of }. rate est le montant entier en IDR pour 1 USD, donc USD = canonical_amount / rate. Les totaux utilisent 2 décimales ; les prix/remboursements par article en utilisent 4. Un montant strictement positif n'est jamais arrondi à 0.00. rate_as_of est l'horodatage RFC3339 du taux (au format +00:00) ou null lorsqu'aucun horodatage n'est enregistré.
v2 uniquement : s'il n'existe aucun taux USD/IDR utilisable, les endpoints monétaires renvoient 503 FX_RATE_UNAVAILABLE avec un en-tête Retry-After au lieu d'un corps monétaire. v1 ne renvoie jamais cela.
/orders/finish Marque une commande comme terminée après réception de l'OTP. Cela libère le numéro immédiatement au lieu d'attendre l'expiration.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de la commande à finaliser |
Exemple de requête
curl -s -X POST https://api.smscode.gg/v2/orders/finish \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v2/orders/finish", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/finish",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} Identique à v1 — seul le chemin de base change (/v1 → /v2).
/orders/resend Demande à la plateforme de renvoyer le SMS au numéro loué. Toutes les plateformes ne prennent pas en charge le renvoi — vérifiez le champ resent dans la réponse.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
id | integer | Oui | Identifiant de la commande pour le renvoi SMS |
Exemple de requête
curl -s -X POST https://api.smscode.gg/v2/orders/resend \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v2/orders/resend", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/resend",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} Identique à v1 — seul le chemin de base change (/v1 → /v2).
/webhook Renvoie votre configuration actuelle de notifications webhook.
Paramètres
Aucun
Exemple de requête
curl -s https://api.smscode.gg/v2/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/webhook", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/webhook",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Identique à v1 — seul le chemin de base change (/v1 → /v2).
/webhook Met à jour votre URL webhook et/ou votre secret. Un secret est généré automatiquement lorsque vous définissez une URL pour la première fois. Envoyez une chaîne vide pour effacer. L'URL doit utiliser HTTPS.
Corps de la requête
| Nom | Type | Requis | Description |
|---|---|---|---|
webhook_url | string | Non | URL HTTPS pour recevoir les événements webhook (chaîne vide pour effacer) |
webhook_secret | string | Non | Secret partagé pour la signature HMAC-SHA256 (généré automatiquement s'il est omis lors de la première configuration) |
Au moins un champ est requis.
Exemple de requête
curl -s -X PATCH https://api.smscode.gg/v2/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://example.com/webhook"}'const res = await fetch("https://api.smscode.gg/v2/webhook", {
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();import requests
res = requests.patch("https://api.smscode.gg/v2/webhook",
json={"webhook_url": "https://example.com/webhook"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Identique à v1 — seul le chemin de base change (/v1 → /v2).
/webhook/test Envoie un événement test à votre URL webhook configurée. Renvoie le code de statut HTTP de votre serveur. Utile pour vérifier que votre endpoint fonctionne avant la mise en production.
Paramètres
Aucun
Exemple de requête
curl -s -X POST https://api.smscode.gg/v2/webhook/test \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/webhook/test", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/webhook/test",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Exemple de réponse
{
"success": true,
"data": {
"status_code": 200
}
} Identique à v1 — seul le chemin de base change (/v1 → /v2).
⟩Notifications webhook
Configurez une URL webhook pour recevoir des notifications push en temps réel pour les événements de commande au lieu du polling. C'est l'approche recommandée pour les scripts de bot.
Événements
| Événement | Déclencheur |
|---|---|
order.otp_received | Code OTP livré au numéro loué |
order.completed | Commande marquée comme terminée (manuellement ou par expiration) |
order.expired | Commande expirée sans OTP (solde remboursé) |
order.canceled | Commande annulée par l'utilisateur (solde remboursé) |
Payload
{
"event": "order.otp_received",
"timestamp": "2026-02-25T12:00:00+00:00",
"data": {
"order_id": 1001,
"phone_number": "+628123456789",
"otp_code": "1234",
"otp_message": "Your verification code is 1234",
"product_id": 42,
"catalog_product_id": 87,
"country": "Indonesia",
"platform": "WhatsApp"
}
} Vérification de la signature
Chaque requête webhook inclut un en-tête X-Webhook-Signature avec une signature HMAC-SHA256 du corps de la requête, utilisant votre webhook_secret comme clé :
Vérifiez cette signature côté serveur pour vous assurer que la requête est authentique. La livraison est de type « fire-and-forget » avec un délai d'expiration de 3 secondes et sans nouvelle tentative.
⟩Limites de débit
Les requêtes API sont soumises à des limites de débit par groupe d'endpoints. Le dépassement de la limite renvoie un code 429 Too Many Requests avec un en-tête Retry-After indiquant le nombre de secondes à attendre.
| Groupe d'endpoints | Limite | Fenêtre |
|---|---|---|
| Catalogue (pays, services, produits, taux de change) | 5 000 requêtes | 60 secondes |
| Solde | 600 requêtes | 60 secondes |
| Lecture de commandes (liste, détail, actives) | 5 000 requêtes | 60 secondes |
| Création de commande | 3 000 requêtes | 60 secondes |
| Annulation de commande | 1 000 requêtes | 60 secondes |
| Actions sur les commandes (finaliser, renvoyer) | 1 000 requêtes | 60 secondes |
| Configuration webhook (lecture, mise à jour) | 600 requêtes | 60 secondes |
| Test webhook | 10 requêtes | 60 secondes |
⟩Codes d'erreur
Les réponses d'erreur incluent l'un de ces codes dans error.code :
| Code | HTTP | Description |
|---|---|---|
UNAUTHORIZED | 401 | Token API manquant ou invalide |
FORBIDDEN | 403 | Accès refusé |
NOT_FOUND | 404 | Ressource introuvable (commande, taux de change, etc.) |
CONFLICT | 409 | Requête en double ou conflit de ressource |
INSUFFICIENT_BALANCE | 409 | Solde insuffisant pour créer la commande |
VALIDATION_ERROR | 422 | Les paramètres de la requête n'ont pas passé la validation |
RATE_LIMIT_EXCEEDED | 429 | Trop de requêtes (vérifiez l'en-tête Retry-After) |
INTERNAL_ERROR | 500 | Erreur interne du serveur |
PROVIDER_ERROR | 422 | Le fournisseur SMS en amont a rejeté la requête. En cas d'échec de création de commande, l'erreur peut inclure <code>details</code> : <code>cause_counts</code> (commandes avec <code>product_id</code> hérité — un décompte regroupé par cause) ou <code>attempts</code> (commandes avec <code>catalog_product_id</code> — résultats par tentative), avec les valeurs <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>. |
NO_OFFER_AVAILABLE | 422 | Aucune offre active ne correspond au produit et à la politique demandés (plafond de prix, disponibilité). |
CANCEL_TOO_EARLY | 409 | Commande trop récente pour être annulée — patientez 2 minutes |
REQUEST_IN_PROGRESS | 409 | Une requête de création avec cette clé d'idempotence est encore en cours |
IDEMPOTENCY_KEY_REUSED | 422 | Cette clé d'idempotence a déjà été utilisée avec un corps de requête différent |
SERVICE_UNAVAILABLE | 503 | Service temporairement indisponible (maintenance) |
FX_RATE_UNAVAILABLE | 503 | Taux de change USD/IDR indisponible (endpoints monétaires v2) — renvoie 503 avec un en-tête Retry-After. |
⟩Migration de v1 vers v2
v1 sert l'IDR ; v2 sert l'USD. Les deux versions coexistent en permanence — il n'y a pas d'arrêt prévu. Choisissez une version par intégration ; ne mélangez pas les chemins de base. v2 est identique à v1, à l'exception de la façon dont l'argent est représenté.
| Aspect | v1 · IDR | v2 · USD |
|---|---|---|
| Champs monétaires | IDR entier, ex. 15000 | Objet monétaire { amount, currency, canonical_amount, canonical_currency } |
meta.fx | Absent | Obligatoire sur chaque réponse porteuse de montant |
| Devise | IDR | USD (codé en dur) |
FX_RATE_UNAVAILABLE | — | Nouveau 503 + Retry-After lorsqu'aucun taux utilisable n'est disponible |
| Précision | — | Totaux 2 décimales, prix/remboursements 4 décimales, arrondi au supérieur pour les positifs |
GET /catalog/exchange-rate | {pair, base_currency, quote_currency, rate} ; prend en compte ?pair | {pair, rate, rate_as_of} ; ?pair ignoré (USD/IDR uniquement) |
Exemples côte à côte
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
}{
"success": true,
"data": {
"balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": 15000,
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1 }
}{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}catalog_product_id est l'identité STABLE du produit (une par pays + plateforme) — vous pouvez la stocker en toute sécurité dans vos systèmes ET commander directement avec : passez la commande avec catalog_product_id (plus les options max_price et prefer_provider) et le serveur résout l'offre active pour vous. La voie par l'id par offre (product_id) reste prise en charge et rétrocompatible — cet id est volatil et change dès qu'un palier de prix du fournisseur évolue, donc le conseil de récupérer à nouveau le catalogue avant de commander ne s'applique qu'à cette voie.
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
}{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}Checklist de migration
- Changez le chemin de base
/v1→/v2. - Analysez les champs monétaires comme des objets — lisez
amountcomme une chaîne décimale ;currencyvaut"USD". - Pour le rapprochement du registre, utilisez
canonical_amount(IDR exact) ; leamounten USD est une projection au moment du rendu et lerateest communiqué une seule fois dansmeta.fx. - Gérez le nouveau
FX_RATE_UNAVAILABLE(503) — réessayez aprèsRetry-After. v1 ne renvoie jamais cela.