API-Dokumentation
Programmatischer Zugriff auf virtuelle Nummern, Bestellungen und Kontoguthaben.
Ueberblick
Alle Geldfelder der /v1-API sind in IDR (indonesische Rupiah), als ganzzahlige Einheiten — zum Beispiel bedeuten "price": 15000 und "balance": 500000 Rp 15.000 und Rp 500.000. Für eine USD-native Projektion desselben Ledgers wechseln Sie über den Versionsschalter oben zur v2-API.
⟩Authentifizierung
Alle API-Anfragen erfordern einen Bearer-Token. Generieren Sie einen in den Kontoeinstellungen im Dashboard und fügen Sie ihn jeder Anfrage hinzu:
Anfragen ohne gültigen Token erhalten eine 401 UNAUTHORIZED-Antwort.
⟩Basis-URL
Alle nachfolgenden Endpunkt-Pfade sind relativ zu:
⟩Antwortformat
Jede Antwort gibt JSON mit einer konsistenten Umschlagsstruktur zurück. Alle Antworten enthalten einen x-request-id-Header für Debugging.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Alle Geldfelder der /v1-API sind in IDR (indonesische Rupiah), als ganzzahlige Einheiten — zum Beispiel bedeuten "price": 15000 und "balance": 500000 Rp 15.000 und Rp 500.000. Für eine USD-native Projektion desselben Ledgers wechseln Sie über den Versionsschalter oben zur v2-API.
/catalog/countries Gibt eine Liste aller verfügbaren Länder zurück.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} /catalog/services Gibt eine Liste der verfügbaren Dienste (Plattformen) zurück. Optional nach Land filterbar.
Query-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
country_id | integer | Nein | Dienste für dieses Land filtern |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} /catalog/products Gibt eine paginierte Liste der verfügbaren Produkte zurück. Filter nach Land und/oder Plattform möglich.
Query-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
country_id | integer | Nein | Nach Länder-ID filtern |
platform_id | integer | Nein | Nach Plattform-/Dienst-ID filtern |
sort | string | Nein | Sortierung: price_asc (Standard), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Nein | Ergebnisse pro Seite (1–10.000, Standard 1.000) |
page | integer | Nein | Seitennummer (min. 1, Standard 1) |
Beispielanfrage
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()Beispielantwort
{
"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 Gibt den aktuellen USD/IDR-Wechselkurs zurück, der für die Währungsumrechnung verwendet wird.
Query-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
pair | string | Nein | Währungspaar (Standard: USD/IDR) |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"pair": "USD/IDR",
"base_currency": "USD",
"quote_currency": "IDR",
"rate": 16250
}
} /balance Gibt das Kontoguthaben des authentifizierten Nutzers zurück.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
} /orders Gibt eine Liste der Bestellungen des authentifizierten Nutzers zurück, sortiert nach Aktualität. Unterstützt Filterung nach Status und Paginierung per Offset.
Query-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
limit | integer | Nein | Max. Ergebnisse (1–100, Standard 20) |
offset | integer | Nein | Anzahl der zu überspringenden Ergebnisse (Standard 0) |
status | string | Nein | Nach Status filtern: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (Groß-/Kleinschreibung egal) |
Beispielanfrage
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()Beispielantwort
{
"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} Gibt eine einzelne Bestellung nach ID zurück. Gibt nur Bestellungen des authentifizierten Nutzers zurück.
Pfad-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID (Pfad-Parameter) |
Beispielanfrage
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()Beispielantwort
{
"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 Listet alle aktuell aktiven Bestellungen auf (ACTIVE + OTP_RECEIVED). Verwenden Sie dies, um den OTP-Status abzufragen.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"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 Erstellt eine neue Bestellung für eine virtuelle Nummer. Guthaben wird automatisch abgebucht. Unterstützt einen optionalen Idempotency-Key-Header, um doppelte Bestellungen bei Netzwerk-Wiederholungen zu verhindern.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
product_id | integer | Nein | ID des zu bestellenden Produkts. Geben Sie NUR EINES an: product_id ODER catalog_product_id, nicht beides. |
catalog_product_id | integer | Nein | Stabile Produkt-ID — der Server wählt das beste aktuelle Angebot (am günstigsten, zuverlässigkeitsbewusst). Geben Sie entweder dies oder product_id an. |
max_price | integer · string | Nein | Optionale Preisobergrenze. v1: IDR-Ganzzahl. v2: USD-Dezimalstring (z. B. "0.50"). |
prefer_provider | string | Nein | Optionaler Anbietercode, der bei gleichwertigen Angeboten bevorzugt wird. |
policy | string | Nein | Optionale Routing-Strategie, nur zusammen mit catalog_product_id gültig. Werte: cheapest (Standard) wählt das günstigste funktionierende Angebot; best_success sortiert Angebote zuerst nach dem jüngsten Zustellerfolg. best_success bewertet jeden Anbieter anhand des Anteils der Bestellungen, die in den letzten 30 abgeschlossenen Tagen ein OTP erhalten haben, in 10%-Stufen, und zählt einen Anbieter erst ab mindestens 20 Bestellungen in diesem Zeitraum — Anbieter unterhalb dieser Schwelle oder ohne Historie gelten als neutral, sodass neue Angebote nie benachteiligt werden (opt-in; das Signal startet neutral). Ist zusätzlich prefer_provider gesetzt, steht der bevorzugte Anbieter weiterhin an erster Stelle. |
quantity | integer | Nein | Anzahl (1–100, Standard 1) |
Übergeben Sie einen Idempotency-Key-Header, um Anfragen sicher zu wiederholen, ohne doppelte Bestellungen zu erstellen. Der Schlüssel darf Buchstaben, Ziffern, Bindestrich und Unterstrich enthalten (A-Z a-z 0-9 _ -), maximal 128 Zeichen; ein ungültiger Schlüssel wird mit 422 VALIDATION_ERROR abgelehnt. Eine Wiederholung mit demselben Schlüssel und demselben Body gibt das ursprüngliche Ergebnis erneut zurück (einschließlich des failed_count bei einem Teilerfolg). Eine Wiederholung, die den Anbieter erreicht, aber fehlschlägt, wird gespeichert und gibt bei erneutem Versuch denselben Fehler zurück — verwenden Sie einen NEUEN Schlüssel für einen weiteren Versuch. Fehler ohne Seiteneffekte (unzureichendes Guthaben, kein verfügbares Angebot) geben den Schlüssel frei, sodass Sie Guthaben aufladen und mit demselben Schlüssel erneut versuchen können. Die Wiederverwendung eines Schlüssels mit einem anderen Body gibt 422 IDEMPOTENCY_KEY_REUSED zurück, und eine noch laufende Anfrage mit diesem Schlüssel gibt 409 REQUEST_IN_PROGRESS zurück. Das Feld failed_reason ist in create-Antworten immer null — es wird nur beim Abfragen/Auflisten von Bestellungen befüllt.
Beispielanfrage
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()Beispielantwort
{
"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 Storniert eine aktive Bestellung. Die Mietkosten werden Ihrem Guthaben gutgeschrieben.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID zum Stornieren |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
} /orders/finish Markiert eine Bestellung als abgeschlossen, nachdem der OTP empfangen wurde. Die Nummer wird sofort freigegeben, anstatt auf den Ablauf zu warten.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID zum Abschließen |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} /orders/resend Fordert die Plattform auf, die SMS erneut an die gemietete Nummer zu senden. Nicht alle Plattformen unterstützen den erneuten Versand — prüfen Sie das Feld "resent" in der Antwort.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID für erneuten SMS-Versand |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} /webhook Gibt Ihre aktuelle Webhook-Benachrichtigungskonfiguration zurück.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook Aktualisiert Ihre Webhook-URL und/oder Ihr Secret. Ein Secret wird automatisch generiert, wenn Sie zum ersten Mal eine URL festlegen. Senden Sie einen leeren String zum Löschen. Die URL muss HTTPS verwenden.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
webhook_url | string | Nein | HTTPS-URL für den Empfang von Webhook-Ereignissen (leerer String zum Löschen) |
webhook_secret | string | Nein | Gemeinsames Secret für HMAC-SHA256-Signatur (wird beim ersten Setzen automatisch generiert, falls nicht angegeben) |
Mindestens ein Feld ist erforderlich.
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook/test Sendet ein Testereignis an Ihre konfigurierte Webhook-URL. Gibt den HTTP-Statuscode Ihres Servers zurück. Nützlich zur Überprüfung, ob Ihr Endpunkt funktioniert, bevor Sie live gehen.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"status_code": 200
}
} ⟩Webhook-Benachrichtigungen
Konfigurieren Sie eine Webhook-URL, um Echtzeit-Push-Benachrichtigungen für Bestellereignisse zu erhalten, anstatt zu pollen. Dies ist der empfohlene Ansatz für Bot-Skripte.
Ereignisse
| Ereignis | Auslöser |
|---|---|
order.otp_received | OTP-Code an die gemietete Nummer zugestellt |
order.completed | Bestellung als abgeschlossen markiert (manuell oder durch Ablauf) |
order.expired | Bestellung ohne OTP abgelaufen (Guthaben erstattet) |
order.canceled | Bestellung vom Nutzer storniert (Guthaben erstattet) |
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"
}
} Signaturverifizierung
Jede Webhook-Anfrage enthält einen X-Webhook-Signature-Header mit einer HMAC-SHA256-Signatur des Anfragekörpers, wobei Ihr webhook_secret als Schlüssel verwendet wird:
Verifizieren Sie diese Signatur auf Ihrem Server, um die Authentizität der Anfrage sicherzustellen. Die Zustellung erfolgt nach dem Fire-and-Forget-Prinzip mit einem 3-Sekunden-Timeout und ohne Wiederholungsversuche.
⟩Rate Limits
API-Anfragen sind pro Endpunkt-Gruppe ratenlimitiert. Bei Überschreitung des Limits wird 429 Too Many Requests mit einem Retry-After-Header zurückgegeben, der angibt, wie viele Sekunden gewartet werden soll.
| Endpunkt-Gruppe | Limit | Zeitfenster |
|---|---|---|
| Katalog (Länder, Dienste, Produkte, Wechselkurs) | 5.000 Anfragen | 60 Sekunden |
| Guthaben | 600 Anfragen | 60 Sekunden |
| Bestellabfragen (Liste, Einzeln, Aktiv) | 5.000 Anfragen | 60 Sekunden |
| Bestellung erstellen | 3.000 Anfragen | 60 Sekunden |
| Bestellung stornieren | 1.000 Anfragen | 60 Sekunden |
| Bestellaktionen (Abschließen, Erneut senden) | 1.000 Anfragen | 60 Sekunden |
| Webhook-Konfiguration (Abrufen, Aktualisieren) | 600 Anfragen | 60 Sekunden |
| Webhook-Test | 10 Anfragen | 60 Sekunden |
⟩Fehlercodes
Fehlerantworten enthalten einen der folgenden Codes in error.code:
| Code | HTTP | Beschreibung |
|---|---|---|
UNAUTHORIZED | 401 | Fehlender oder ungültiger API-Token |
FORBIDDEN | 403 | Zugriff verweigert |
NOT_FOUND | 404 | Ressource nicht gefunden (Bestellung, Wechselkurs usw.) |
CONFLICT | 409 | Doppelte Anfrage oder Ressourcenkonflikt |
INSUFFICIENT_BALANCE | 409 | Unzureichendes Guthaben für die Bestellung |
VALIDATION_ERROR | 422 | Anfrageparameter haben die Validierung nicht bestanden |
RATE_LIMIT_EXCEEDED | 429 | Zu viele Anfragen (prüfen Sie den Retry-After-Header) |
INTERNAL_ERROR | 500 | Interner Serverfehler |
PROVIDER_ERROR | 422 | Vorgelagerter SMS-Anbieter hat die Anfrage abgelehnt. Bei fehlgeschlagener Bestellerstellung kann der Fehler <code>details</code> enthalten: <code>cause_counts</code> (Bestellungen mit altem <code>product_id</code> — eine nach Ursache gruppierte Zählung) oder <code>attempts</code> (Bestellungen mit <code>catalog_product_id</code> — Ergebnisse pro Versuch), mit den Werten <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 | Kein aktives Angebot passt zum angeforderten Produkt und zur Richtlinie (Preisobergrenze, Verfügbarkeit). |
CANCEL_TOO_EARLY | 409 | Bestellung zu neu zum Stornieren — warten Sie 2 Minuten |
REQUEST_IN_PROGRESS | 409 | Eine Erstellungsanfrage mit diesem Idempotenzschlüssel läuft noch |
IDEMPOTENCY_KEY_REUSED | 422 | Dieser Idempotenzschlüssel wurde bereits mit einem anderen Anfrage-Body verwendet |
SERVICE_UNAVAILABLE | 503 | Dienst vorübergehend nicht verfügbar (Wartung) |
Ueberblick
Alle Geldfelder der /v2-API sind in USD und werden als Geldobjekt zurückgegeben — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount ist ein dezimaler String; canonical_amount ist der exakte IDR-Ledgerwert (verwenden Sie ihn zur Abstimmung). Der angewandte USD/IDR-rate wird einmal pro Antwort in meta.fx offengelegt. v2 ist eine USD-Projektion zur Renderzeit über demselben IDR-Ledger wie v1 — sie speichert oder verbucht niemals USD.
⟩Authentifizierung
Alle API-Anfragen erfordern einen Bearer-Token. Generieren Sie einen in den Kontoeinstellungen im Dashboard und fügen Sie ihn jeder Anfrage hinzu:
Anfragen ohne gültigen Token erhalten eine 401 UNAUTHORIZED-Antwort.
⟩Basis-URL
Alle nachfolgenden Endpunkt-Pfade sind relativ zu:
⟩Antwortformat
Jede Antwort gibt JSON mit einer konsistenten Umschlagsstruktur zurück. Alle Antworten enthalten einen x-request-id-Header für Debugging.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} v2-Fehler: kein verwendbarer Wechselkurs (503)
{
"success": false,
"error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
} Alle Geldfelder der /v2-API sind in USD und werden als Geldobjekt zurückgegeben — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount ist ein dezimaler String; canonical_amount ist der exakte IDR-Ledgerwert (verwenden Sie ihn zur Abstimmung). Der angewandte USD/IDR-rate wird einmal pro Antwort in meta.fx offengelegt. v2 ist eine USD-Projektion zur Renderzeit über demselben IDR-Ledger wie v1 — sie speichert oder verbucht niemals USD.
/catalog/countries Gibt eine Liste aller verfügbaren Länder zurück.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} Identisch mit v1 — nur der Basispfad ändert sich (/v1 → /v2).
/catalog/services Gibt eine Liste der verfügbaren Dienste (Plattformen) zurück. Optional nach Land filterbar.
Query-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
country_id | integer | Nein | Dienste für dieses Land filtern |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} Identisch mit v1 — nur der Basispfad ändert sich (/v1 → /v2).
/catalog/products Gibt eine paginierte Liste der verfügbaren Produkte zurück. Filter nach Land und/oder Plattform möglich.
Query-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
country_id | integer | Nein | Nach Länder-ID filtern |
platform_id | integer | Nein | Nach Plattform-/Dienst-ID filtern |
sort | string | Nein | Sortierung: price_asc (Standard), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Nein | Ergebnisse pro Seite (1–10.000, Standard 1.000) |
page | integer | Nein | Seitennummer (min. 1, Standard 1) |
Beispielanfrage
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()Beispielantwort
{
"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: Geldfelder sind USD-Geldobjekte, und die Antwort enthält ein einzelnes meta.fx { pair, rate, rate_as_of }. rate ist die ganzzahlige IDR-Menge pro 1 USD, also USD = canonical_amount / rate. Summen verwenden 2 Dezimalstellen; Einzelpreise/Erstattungen verwenden 4. Ein streng positiver Betrag wird niemals auf 0.00 gerundet. rate_as_of ist der RFC3339-Zeitstempel des Kurses (Form +00:00) oder null, wenn kein Zeitstempel erfasst ist.
Nur v2: Existiert kein verwendbarer USD/IDR-Kurs, geben Geld-Endpoints 503 FX_RATE_UNAVAILABLE mit einem Retry-After-Header statt eines Geld-Bodys zurück. v1 gibt dies niemals zurück.
/catalog/exchange-rate Gibt den aktuellen USD/IDR-Wechselkurs zurück, der für die Währungsumrechnung verwendet wird.
Parameter
Keine — v2 gibt immer USD/IDR zurück; der ?pair-Parameter von v1 wird ignoriert.
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
} v2: gibt { pair, rate, rate_as_of } zurück (kein base_currency/quote_currency, kein meta-Wrapper — der Kurs ist die Daten). ?pair wird ignoriert — v2 gibt immer USD/IDR zurück (v1 berücksichtigt ?pair). Gibt 503 FX_RATE_UNAVAILABLE zurück, wenn kein verwendbarer Kurs existiert.
/balance Gibt das Kontoguthaben des authentifizierten Nutzers zurück.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"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: Geldfelder sind USD-Geldobjekte, und die Antwort enthält ein einzelnes meta.fx { pair, rate, rate_as_of }. rate ist die ganzzahlige IDR-Menge pro 1 USD, also USD = canonical_amount / rate. Summen verwenden 2 Dezimalstellen; Einzelpreise/Erstattungen verwenden 4. Ein streng positiver Betrag wird niemals auf 0.00 gerundet. rate_as_of ist der RFC3339-Zeitstempel des Kurses (Form +00:00) oder null, wenn kein Zeitstempel erfasst ist.
Nur v2: Existiert kein verwendbarer USD/IDR-Kurs, geben Geld-Endpoints 503 FX_RATE_UNAVAILABLE mit einem Retry-After-Header statt eines Geld-Bodys zurück. v1 gibt dies niemals zurück.
/orders Gibt eine Liste der Bestellungen des authentifizierten Nutzers zurück, sortiert nach Aktualität. Unterstützt Filterung nach Status und Paginierung per Offset.
Query-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
limit | integer | Nein | Max. Ergebnisse (1–100, Standard 20) |
offset | integer | Nein | Anzahl der zu überspringenden Ergebnisse (Standard 0) |
status | string | Nein | Nach Status filtern: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (Groß-/Kleinschreibung egal) |
Beispielanfrage
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()Beispielantwort
{
"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: Geldfelder sind USD-Geldobjekte, und die Antwort enthält ein einzelnes meta.fx { pair, rate, rate_as_of }. rate ist die ganzzahlige IDR-Menge pro 1 USD, also USD = canonical_amount / rate. Summen verwenden 2 Dezimalstellen; Einzelpreise/Erstattungen verwenden 4. Ein streng positiver Betrag wird niemals auf 0.00 gerundet. rate_as_of ist der RFC3339-Zeitstempel des Kurses (Form +00:00) oder null, wenn kein Zeitstempel erfasst ist.
Nur v2: Existiert kein verwendbarer USD/IDR-Kurs, geben Geld-Endpoints 503 FX_RATE_UNAVAILABLE mit einem Retry-After-Header statt eines Geld-Bodys zurück. v1 gibt dies niemals zurück.
/orders/{id} Gibt eine einzelne Bestellung nach ID zurück. Gibt nur Bestellungen des authentifizierten Nutzers zurück.
Pfad-Parameter
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID (Pfad-Parameter) |
Beispielanfrage
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()Beispielantwort
{
"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: Geldfelder sind USD-Geldobjekte, und die Antwort enthält ein einzelnes meta.fx { pair, rate, rate_as_of }. rate ist die ganzzahlige IDR-Menge pro 1 USD, also USD = canonical_amount / rate. Summen verwenden 2 Dezimalstellen; Einzelpreise/Erstattungen verwenden 4. Ein streng positiver Betrag wird niemals auf 0.00 gerundet. rate_as_of ist der RFC3339-Zeitstempel des Kurses (Form +00:00) oder null, wenn kein Zeitstempel erfasst ist.
Nur v2: Existiert kein verwendbarer USD/IDR-Kurs, geben Geld-Endpoints 503 FX_RATE_UNAVAILABLE mit einem Retry-After-Header statt eines Geld-Bodys zurück. v1 gibt dies niemals zurück.
/orders/active Listet alle aktuell aktiven Bestellungen auf (ACTIVE + OTP_RECEIVED). Verwenden Sie dies, um den OTP-Status abzufragen.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"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: Dieser Endpoint ist nicht geldführend — er gibt weder amount noch meta.fx zurück (dieselbe Struktur wie v1, unter /v2).
/orders/create Erstellt eine neue Bestellung für eine virtuelle Nummer. Guthaben wird automatisch abgebucht. Unterstützt einen optionalen Idempotency-Key-Header, um doppelte Bestellungen bei Netzwerk-Wiederholungen zu verhindern.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
product_id | integer | Nein | ID des zu bestellenden Produkts. Geben Sie NUR EINES an: product_id ODER catalog_product_id, nicht beides. |
catalog_product_id | integer | Nein | Stabile Produkt-ID — der Server wählt das beste aktuelle Angebot (am günstigsten, zuverlässigkeitsbewusst). Geben Sie entweder dies oder product_id an. |
max_price | integer · string | Nein | Optionale Preisobergrenze. v1: IDR-Ganzzahl. v2: USD-Dezimalstring (z. B. "0.50"). |
prefer_provider | string | Nein | Optionaler Anbietercode, der bei gleichwertigen Angeboten bevorzugt wird. |
policy | string | Nein | Optionale Routing-Strategie, nur zusammen mit catalog_product_id gültig. Werte: cheapest (Standard) wählt das günstigste funktionierende Angebot; best_success sortiert Angebote zuerst nach dem jüngsten Zustellerfolg. best_success bewertet jeden Anbieter anhand des Anteils der Bestellungen, die in den letzten 30 abgeschlossenen Tagen ein OTP erhalten haben, in 10%-Stufen, und zählt einen Anbieter erst ab mindestens 20 Bestellungen in diesem Zeitraum — Anbieter unterhalb dieser Schwelle oder ohne Historie gelten als neutral, sodass neue Angebote nie benachteiligt werden (opt-in; das Signal startet neutral). Ist zusätzlich prefer_provider gesetzt, steht der bevorzugte Anbieter weiterhin an erster Stelle. |
quantity | integer | Nein | Anzahl (1–100, Standard 1) |
Übergeben Sie einen Idempotency-Key-Header, um Anfragen sicher zu wiederholen, ohne doppelte Bestellungen zu erstellen. Der Schlüssel darf Buchstaben, Ziffern, Bindestrich und Unterstrich enthalten (A-Z a-z 0-9 _ -), maximal 128 Zeichen; ein ungültiger Schlüssel wird mit 422 VALIDATION_ERROR abgelehnt. Eine Wiederholung mit demselben Schlüssel und demselben Body gibt das ursprüngliche Ergebnis erneut zurück (einschließlich des failed_count bei einem Teilerfolg). Eine Wiederholung, die den Anbieter erreicht, aber fehlschlägt, wird gespeichert und gibt bei erneutem Versuch denselben Fehler zurück — verwenden Sie einen NEUEN Schlüssel für einen weiteren Versuch. Fehler ohne Seiteneffekte (unzureichendes Guthaben, kein verfügbares Angebot) geben den Schlüssel frei, sodass Sie Guthaben aufladen und mit demselben Schlüssel erneut versuchen können. Die Wiederverwendung eines Schlüssels mit einem anderen Body gibt 422 IDEMPOTENCY_KEY_REUSED zurück, und eine noch laufende Anfrage mit diesem Schlüssel gibt 409 REQUEST_IN_PROGRESS zurück. Das Feld failed_reason ist in create-Antworten immer null — es wird nur beim Abfragen/Auflisten von Bestellungen befüllt.
Beispielanfrage
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()Beispielantwort
{
"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: Geldfelder sind USD-Geldobjekte, und die Antwort enthält ein einzelnes meta.fx { pair, rate, rate_as_of }. rate ist die ganzzahlige IDR-Menge pro 1 USD, also USD = canonical_amount / rate. Summen verwenden 2 Dezimalstellen; Einzelpreise/Erstattungen verwenden 4. Ein streng positiver Betrag wird niemals auf 0.00 gerundet. rate_as_of ist der RFC3339-Zeitstempel des Kurses (Form +00:00) oder null, wenn kein Zeitstempel erfasst ist.
Nur v2: Existiert kein verwendbarer USD/IDR-Kurs, geben Geld-Endpoints 503 FX_RATE_UNAVAILABLE mit einem Retry-After-Header statt eines Geld-Bodys zurück. v1 gibt dies niemals zurück.
/orders/cancel Storniert eine aktive Bestellung. Die Mietkosten werden Ihrem Guthaben gutgeschrieben.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID zum Stornieren |
Beispielanfrage
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()Beispielantwort
{
"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: Geldfelder sind USD-Geldobjekte, und die Antwort enthält ein einzelnes meta.fx { pair, rate, rate_as_of }. rate ist die ganzzahlige IDR-Menge pro 1 USD, also USD = canonical_amount / rate. Summen verwenden 2 Dezimalstellen; Einzelpreise/Erstattungen verwenden 4. Ein streng positiver Betrag wird niemals auf 0.00 gerundet. rate_as_of ist der RFC3339-Zeitstempel des Kurses (Form +00:00) oder null, wenn kein Zeitstempel erfasst ist.
Nur v2: Existiert kein verwendbarer USD/IDR-Kurs, geben Geld-Endpoints 503 FX_RATE_UNAVAILABLE mit einem Retry-After-Header statt eines Geld-Bodys zurück. v1 gibt dies niemals zurück.
/orders/finish Markiert eine Bestellung als abgeschlossen, nachdem der OTP empfangen wurde. Die Nummer wird sofort freigegeben, anstatt auf den Ablauf zu warten.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID zum Abschließen |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} Identisch mit v1 — nur der Basispfad ändert sich (/v1 → /v2).
/orders/resend Fordert die Plattform auf, die SMS erneut an die gemietete Nummer zu senden. Nicht alle Plattformen unterstützen den erneuten Versand — prüfen Sie das Feld "resent" in der Antwort.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | integer | Ja | Bestell-ID für erneuten SMS-Versand |
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} Identisch mit v1 — nur der Basispfad ändert sich (/v1 → /v2).
/webhook Gibt Ihre aktuelle Webhook-Benachrichtigungskonfiguration zurück.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Identisch mit v1 — nur der Basispfad ändert sich (/v1 → /v2).
/webhook Aktualisiert Ihre Webhook-URL und/oder Ihr Secret. Ein Secret wird automatisch generiert, wenn Sie zum ersten Mal eine URL festlegen. Senden Sie einen leeren String zum Löschen. Die URL muss HTTPS verwenden.
Anfragekörper
| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
webhook_url | string | Nein | HTTPS-URL für den Empfang von Webhook-Ereignissen (leerer String zum Löschen) |
webhook_secret | string | Nein | Gemeinsames Secret für HMAC-SHA256-Signatur (wird beim ersten Setzen automatisch generiert, falls nicht angegeben) |
Mindestens ein Feld ist erforderlich.
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Identisch mit v1 — nur der Basispfad ändert sich (/v1 → /v2).
/webhook/test Sendet ein Testereignis an Ihre konfigurierte Webhook-URL. Gibt den HTTP-Statuscode Ihres Servers zurück. Nützlich zur Überprüfung, ob Ihr Endpunkt funktioniert, bevor Sie live gehen.
Parameter
Keine
Beispielanfrage
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()Beispielantwort
{
"success": true,
"data": {
"status_code": 200
}
} Identisch mit v1 — nur der Basispfad ändert sich (/v1 → /v2).
⟩Webhook-Benachrichtigungen
Konfigurieren Sie eine Webhook-URL, um Echtzeit-Push-Benachrichtigungen für Bestellereignisse zu erhalten, anstatt zu pollen. Dies ist der empfohlene Ansatz für Bot-Skripte.
Ereignisse
| Ereignis | Auslöser |
|---|---|
order.otp_received | OTP-Code an die gemietete Nummer zugestellt |
order.completed | Bestellung als abgeschlossen markiert (manuell oder durch Ablauf) |
order.expired | Bestellung ohne OTP abgelaufen (Guthaben erstattet) |
order.canceled | Bestellung vom Nutzer storniert (Guthaben erstattet) |
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"
}
} Signaturverifizierung
Jede Webhook-Anfrage enthält einen X-Webhook-Signature-Header mit einer HMAC-SHA256-Signatur des Anfragekörpers, wobei Ihr webhook_secret als Schlüssel verwendet wird:
Verifizieren Sie diese Signatur auf Ihrem Server, um die Authentizität der Anfrage sicherzustellen. Die Zustellung erfolgt nach dem Fire-and-Forget-Prinzip mit einem 3-Sekunden-Timeout und ohne Wiederholungsversuche.
⟩Rate Limits
API-Anfragen sind pro Endpunkt-Gruppe ratenlimitiert. Bei Überschreitung des Limits wird 429 Too Many Requests mit einem Retry-After-Header zurückgegeben, der angibt, wie viele Sekunden gewartet werden soll.
| Endpunkt-Gruppe | Limit | Zeitfenster |
|---|---|---|
| Katalog (Länder, Dienste, Produkte, Wechselkurs) | 5.000 Anfragen | 60 Sekunden |
| Guthaben | 600 Anfragen | 60 Sekunden |
| Bestellabfragen (Liste, Einzeln, Aktiv) | 5.000 Anfragen | 60 Sekunden |
| Bestellung erstellen | 3.000 Anfragen | 60 Sekunden |
| Bestellung stornieren | 1.000 Anfragen | 60 Sekunden |
| Bestellaktionen (Abschließen, Erneut senden) | 1.000 Anfragen | 60 Sekunden |
| Webhook-Konfiguration (Abrufen, Aktualisieren) | 600 Anfragen | 60 Sekunden |
| Webhook-Test | 10 Anfragen | 60 Sekunden |
⟩Fehlercodes
Fehlerantworten enthalten einen der folgenden Codes in error.code:
| Code | HTTP | Beschreibung |
|---|---|---|
UNAUTHORIZED | 401 | Fehlender oder ungültiger API-Token |
FORBIDDEN | 403 | Zugriff verweigert |
NOT_FOUND | 404 | Ressource nicht gefunden (Bestellung, Wechselkurs usw.) |
CONFLICT | 409 | Doppelte Anfrage oder Ressourcenkonflikt |
INSUFFICIENT_BALANCE | 409 | Unzureichendes Guthaben für die Bestellung |
VALIDATION_ERROR | 422 | Anfrageparameter haben die Validierung nicht bestanden |
RATE_LIMIT_EXCEEDED | 429 | Zu viele Anfragen (prüfen Sie den Retry-After-Header) |
INTERNAL_ERROR | 500 | Interner Serverfehler |
PROVIDER_ERROR | 422 | Vorgelagerter SMS-Anbieter hat die Anfrage abgelehnt. Bei fehlgeschlagener Bestellerstellung kann der Fehler <code>details</code> enthalten: <code>cause_counts</code> (Bestellungen mit altem <code>product_id</code> — eine nach Ursache gruppierte Zählung) oder <code>attempts</code> (Bestellungen mit <code>catalog_product_id</code> — Ergebnisse pro Versuch), mit den Werten <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 | Kein aktives Angebot passt zum angeforderten Produkt und zur Richtlinie (Preisobergrenze, Verfügbarkeit). |
CANCEL_TOO_EARLY | 409 | Bestellung zu neu zum Stornieren — warten Sie 2 Minuten |
REQUEST_IN_PROGRESS | 409 | Eine Erstellungsanfrage mit diesem Idempotenzschlüssel läuft noch |
IDEMPOTENCY_KEY_REUSED | 422 | Dieser Idempotenzschlüssel wurde bereits mit einem anderen Anfrage-Body verwendet |
SERVICE_UNAVAILABLE | 503 | Dienst vorübergehend nicht verfügbar (Wartung) |
FX_RATE_UNAVAILABLE | 503 | USD/IDR-Wechselkurs nicht verfügbar (v2-Geld-Endpoints) — gibt 503 mit einem Retry-After-Header zurück. |
⟩Migration von v1 zu v2
v1 liefert IDR; v2 liefert USD. Beide Versionen existieren dauerhaft parallel — es gibt keine Abschaltung. Wählen Sie pro Integration eine Version; mischen Sie keine Basispfade. v2 ist identisch mit v1, abgesehen davon, wie Geld dargestellt wird.
| Aspekt | v1 · IDR | v2 · USD |
|---|---|---|
| Geldfelder | Ganzzahliges IDR, z. B. 15000 | Geldobjekt { amount, currency, canonical_amount, canonical_currency } |
meta.fx | Nicht vorhanden | Bei jeder geldführenden Antwort erforderlich |
| Währung | IDR | USD (fest codiert) |
FX_RATE_UNAVAILABLE | — | Neuer 503 + Retry-After, wenn kein verwendbarer Kurs vorliegt |
| Präzision | — | Summen 2 Stellen, Preise/Erstattungen 4 Stellen, Aufrundung bei positiven Werten |
GET /catalog/exchange-rate | {pair, base_currency, quote_currency, rate}; berücksichtigt ?pair | {pair, rate, rate_as_of}; ?pair ignoriert (nur USD/IDR) |
Beispiele im Vergleich
{
"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 ist die STABILE Produktidentität (eine pro Land + Plattform) — sie kann sicher in Ihren Systemen gespeichert werden UND ist direkt bestellbar: Bestellen Sie mit catalog_product_id (plus optional max_price und prefer_provider), und der Server ermittelt das aktuelle Angebot für Sie. Der Weg über die einzelne product_id wird weiterhin unterstützt und ist abwärtskompatibel — diese ID ist flüchtig und ändert sich, sobald sich eine Preisstufe des Anbieters verschiebt; der Hinweis, den Katalog vor jeder Bestellung erneut abzuholen, gilt daher nur für diesen Weg.
{
"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" } }
}Migrations-Checkliste
- Wechseln Sie den Basispfad
/v1→/v2. - Parsen Sie Geldfelder als Objekte — lesen Sie
amountals Dezimal-String;currencyist"USD". - Verwenden Sie für die Ledger-Abstimmung
canonical_amount(exaktes IDR); der USD-amountist eine Projektion zur Renderzeit, und derratewird einmal inmeta.fxoffengelegt. - Behandeln Sie den neuen
FX_RATE_UNAVAILABLE(503) — wiederholen Sie die Anfrage nachRetry-After. v1 gibt dies niemals zurück.