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:

Authorization: Bearer YOUR_API_TOKEN

Anfragen ohne gültigen Token erhalten eine 401 UNAUTHORIZED-Antwort.

Basis-URL

Alle nachfolgenden Endpunkt-Pfade sind relativ zu:

https://api.smscode.gg/v1

Antwortformat

Jede Antwort gibt JSON mit einer konsistenten Umschlagsstruktur zurück. Alle Antworten enthalten einen x-request-id-Header für Debugging.

Erfolg
{
  "success": true,
  "data": { ... }
}
Fehler
{
  "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.

GET /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

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}
GET /catalog/services

Gibt eine Liste der verfügbaren Dienste (Plattformen) zurück. Optional nach Land filterbar.

Query-Parameter

NameTypErforderlichBeschreibung
country_idintegerNeinDienste 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

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}
GET /catalog/products

Gibt eine paginierte Liste der verfügbaren Produkte zurück. Filter nach Land und/oder Plattform möglich.

Query-Parameter

NameTypErforderlichBeschreibung
country_idintegerNeinNach Länder-ID filtern
platform_idintegerNeinNach Plattform-/Dienst-ID filtern
sortstringNeinSortierung: price_asc (Standard), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerNeinErgebnisse pro Seite (1–10.000, Standard 1.000)
pageintegerNeinSeitennummer (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

200 OK
{
  "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 }
}
GET /catalog/exchange-rate

Gibt den aktuellen USD/IDR-Wechselkurs zurück, der für die Währungsumrechnung verwendet wird.

Query-Parameter

NameTypErforderlichBeschreibung
pairstringNeinWä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

200 OK
{
  "success": true,
  "data": {
    "pair": "USD/IDR",
    "base_currency": "USD",
    "quote_currency": "IDR",
    "rate": 16250
  }
}
GET /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

200 OK
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
GET /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

NameTypErforderlichBeschreibung
limitintegerNeinMax. Ergebnisse (1–100, Standard 20)
offsetintegerNeinAnzahl der zu überspringenden Ergebnisse (Standard 0)
statusstringNeinNach 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

200 OK
{
  "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
    }
  ]
}
GET /orders/{id}

Gibt eine einzelne Bestellung nach ID zurück. Gibt nur Bestellungen des authentifizierten Nutzers zurück.

Pfad-Parameter

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "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
  }
}
GET /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

200 OK
{
  "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
    }
  ]
}
POST /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

NameTypErforderlichBeschreibung
product_idintegerNeinID des zu bestellenden Produkts. Geben Sie NUR EINES an: product_id ODER catalog_product_id, nicht beides.
catalog_product_idintegerNeinStabile Produkt-ID — der Server wählt das beste aktuelle Angebot (am günstigsten, zuverlässigkeitsbewusst). Geben Sie entweder dies oder product_id an.
max_priceinteger · stringNeinOptionale Preisobergrenze. v1: IDR-Ganzzahl. v2: USD-Dezimalstring (z. B. "0.50").
prefer_providerstringNeinOptionaler Anbietercode, der bei gleichwertigen Angeboten bevorzugt wird.
policystringNeinOptionale 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.
quantityintegerNeinAnzahl (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

200 OK
{
  "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
  }
}
POST /orders/cancel

Storniert eine aktive Bestellung. Die Mietkosten werden Ihrem Guthaben gutgeschrieben.

Anfragekörper

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
POST /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

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}
POST /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

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}
GET /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

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
PATCH /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

NameTypErforderlichBeschreibung
webhook_urlstringNeinHTTPS-URL für den Empfang von Webhook-Ereignissen (leerer String zum Löschen)
webhook_secretstringNeinGemeinsames 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

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
POST /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

200 OK
{
  "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

EreignisAuslöser
order.otp_receivedOTP-Code an die gemietete Nummer zugestellt
order.completedBestellung als abgeschlossen markiert (manuell oder durch Ablauf)
order.expiredBestellung ohne OTP abgelaufen (Guthaben erstattet)
order.canceledBestellung vom Nutzer storniert (Guthaben erstattet)

Payload

Webhook-POST-Body
{
  "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:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

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-GruppeLimitZeitfenster
Katalog (Länder, Dienste, Produkte, Wechselkurs)5.000 Anfragen60 Sekunden
Guthaben600 Anfragen60 Sekunden
Bestellabfragen (Liste, Einzeln, Aktiv)5.000 Anfragen60 Sekunden
Bestellung erstellen3.000 Anfragen60 Sekunden
Bestellung stornieren1.000 Anfragen60 Sekunden
Bestellaktionen (Abschließen, Erneut senden)1.000 Anfragen60 Sekunden
Webhook-Konfiguration (Abrufen, Aktualisieren)600 Anfragen60 Sekunden
Webhook-Test10 Anfragen60 Sekunden

Fehlercodes

Fehlerantworten enthalten einen der folgenden Codes in error.code:

CodeHTTPBeschreibung
UNAUTHORIZED401Fehlender oder ungültiger API-Token
FORBIDDEN403Zugriff verweigert
NOT_FOUND404Ressource nicht gefunden (Bestellung, Wechselkurs usw.)
CONFLICT409Doppelte Anfrage oder Ressourcenkonflikt
INSUFFICIENT_BALANCE409Unzureichendes Guthaben für die Bestellung
VALIDATION_ERROR422Anfrageparameter haben die Validierung nicht bestanden
RATE_LIMIT_EXCEEDED429Zu viele Anfragen (prüfen Sie den Retry-After-Header)
INTERNAL_ERROR500Interner Serverfehler
PROVIDER_ERROR422Vorgelagerter 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_AVAILABLE422Kein aktives Angebot passt zum angeforderten Produkt und zur Richtlinie (Preisobergrenze, Verfügbarkeit).
CANCEL_TOO_EARLY409Bestellung zu neu zum Stornieren — warten Sie 2 Minuten
REQUEST_IN_PROGRESS409Eine Erstellungsanfrage mit diesem Idempotenzschlüssel läuft noch
IDEMPOTENCY_KEY_REUSED422Dieser Idempotenzschlüssel wurde bereits mit einem anderen Anfrage-Body verwendet
SERVICE_UNAVAILABLE503Dienst 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.

Migration von v1 zu v2

Authentifizierung

Alle API-Anfragen erfordern einen Bearer-Token. Generieren Sie einen in den Kontoeinstellungen im Dashboard und fügen Sie ihn jeder Anfrage hinzu:

Authorization: Bearer YOUR_API_TOKEN

Anfragen ohne gültigen Token erhalten eine 401 UNAUTHORIZED-Antwort.

Basis-URL

Alle nachfolgenden Endpunkt-Pfade sind relativ zu:

https://api.smscode.gg/v2

Antwortformat

Jede Antwort gibt JSON mit einer konsistenten Umschlagsstruktur zurück. Alle Antworten enthalten einen x-request-id-Header für Debugging.

Erfolg
{
  "success": true,
  "data": { ... }
}
Fehler
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

v2-Fehler: kein verwendbarer Wechselkurs (503)

503 Service Unavailable · Retry-After: 60
{
  "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.

GET /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

200 OK
{
  "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).

GET /catalog/services

Gibt eine Liste der verfügbaren Dienste (Plattformen) zurück. Optional nach Land filterbar.

Query-Parameter

NameTypErforderlichBeschreibung
country_idintegerNeinDienste 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

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}

Identisch mit v1 — nur der Basispfad ändert sich (/v1/v2).

GET /catalog/products

Gibt eine paginierte Liste der verfügbaren Produkte zurück. Filter nach Land und/oder Plattform möglich.

Query-Parameter

NameTypErforderlichBeschreibung
country_idintegerNeinNach Länder-ID filtern
platform_idintegerNeinNach Plattform-/Dienst-ID filtern
sortstringNeinSortierung: price_asc (Standard), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerNeinErgebnisse pro Seite (1–10.000, Standard 1.000)
pageintegerNeinSeitennummer (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

200 OK
{
  "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.

GET /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

200 OK
{
  "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.

GET /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

200 OK
{
  "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.

GET /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

NameTypErforderlichBeschreibung
limitintegerNeinMax. Ergebnisse (1–100, Standard 20)
offsetintegerNeinAnzahl der zu überspringenden Ergebnisse (Standard 0)
statusstringNeinNach 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

200 OK
{
  "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.

GET /orders/{id}

Gibt eine einzelne Bestellung nach ID zurück. Gibt nur Bestellungen des authentifizierten Nutzers zurück.

Pfad-Parameter

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "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.

GET /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

200 OK
{
  "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).

POST /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

NameTypErforderlichBeschreibung
product_idintegerNeinID des zu bestellenden Produkts. Geben Sie NUR EINES an: product_id ODER catalog_product_id, nicht beides.
catalog_product_idintegerNeinStabile Produkt-ID — der Server wählt das beste aktuelle Angebot (am günstigsten, zuverlässigkeitsbewusst). Geben Sie entweder dies oder product_id an.
max_priceinteger · stringNeinOptionale Preisobergrenze. v1: IDR-Ganzzahl. v2: USD-Dezimalstring (z. B. "0.50").
prefer_providerstringNeinOptionaler Anbietercode, der bei gleichwertigen Angeboten bevorzugt wird.
policystringNeinOptionale 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.
quantityintegerNeinAnzahl (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

200 OK
{
  "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.

POST /orders/cancel

Storniert eine aktive Bestellung. Die Mietkosten werden Ihrem Guthaben gutgeschrieben.

Anfragekörper

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "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.

POST /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

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}

Identisch mit v1 — nur der Basispfad ändert sich (/v1/v2).

POST /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

NameTypErforderlichBeschreibung
idintegerJaBestell-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

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}

Identisch mit v1 — nur der Basispfad ändert sich (/v1/v2).

GET /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

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

Identisch mit v1 — nur der Basispfad ändert sich (/v1/v2).

PATCH /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

NameTypErforderlichBeschreibung
webhook_urlstringNeinHTTPS-URL für den Empfang von Webhook-Ereignissen (leerer String zum Löschen)
webhook_secretstringNeinGemeinsames 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

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

Identisch mit v1 — nur der Basispfad ändert sich (/v1/v2).

POST /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

200 OK
{
  "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

EreignisAuslöser
order.otp_receivedOTP-Code an die gemietete Nummer zugestellt
order.completedBestellung als abgeschlossen markiert (manuell oder durch Ablauf)
order.expiredBestellung ohne OTP abgelaufen (Guthaben erstattet)
order.canceledBestellung vom Nutzer storniert (Guthaben erstattet)

Payload

Webhook-POST-Body
{
  "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:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

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-GruppeLimitZeitfenster
Katalog (Länder, Dienste, Produkte, Wechselkurs)5.000 Anfragen60 Sekunden
Guthaben600 Anfragen60 Sekunden
Bestellabfragen (Liste, Einzeln, Aktiv)5.000 Anfragen60 Sekunden
Bestellung erstellen3.000 Anfragen60 Sekunden
Bestellung stornieren1.000 Anfragen60 Sekunden
Bestellaktionen (Abschließen, Erneut senden)1.000 Anfragen60 Sekunden
Webhook-Konfiguration (Abrufen, Aktualisieren)600 Anfragen60 Sekunden
Webhook-Test10 Anfragen60 Sekunden

Fehlercodes

Fehlerantworten enthalten einen der folgenden Codes in error.code:

CodeHTTPBeschreibung
UNAUTHORIZED401Fehlender oder ungültiger API-Token
FORBIDDEN403Zugriff verweigert
NOT_FOUND404Ressource nicht gefunden (Bestellung, Wechselkurs usw.)
CONFLICT409Doppelte Anfrage oder Ressourcenkonflikt
INSUFFICIENT_BALANCE409Unzureichendes Guthaben für die Bestellung
VALIDATION_ERROR422Anfrageparameter haben die Validierung nicht bestanden
RATE_LIMIT_EXCEEDED429Zu viele Anfragen (prüfen Sie den Retry-After-Header)
INTERNAL_ERROR500Interner Serverfehler
PROVIDER_ERROR422Vorgelagerter 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_AVAILABLE422Kein aktives Angebot passt zum angeforderten Produkt und zur Richtlinie (Preisobergrenze, Verfügbarkeit).
CANCEL_TOO_EARLY409Bestellung zu neu zum Stornieren — warten Sie 2 Minuten
REQUEST_IN_PROGRESS409Eine Erstellungsanfrage mit diesem Idempotenzschlüssel läuft noch
IDEMPOTENCY_KEY_REUSED422Dieser Idempotenzschlüssel wurde bereits mit einem anderen Anfrage-Body verwendet
SERVICE_UNAVAILABLE503Dienst vorübergehend nicht verfügbar (Wartung)
FX_RATE_UNAVAILABLE503USD/IDR-Wechselkurs nicht verfügbar (v2-Geld-Endpoints) — gibt 503 mit einem Retry-After-Header zurück.
v1 → v2

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.

Aspektv1 · IDRv2 · USD
GeldfelderGanzzahliges IDR, z. B. 15000Geldobjekt { amount, currency, canonical_amount, canonical_currency }
meta.fxNicht vorhandenBei jeder geldführenden Antwort erforderlich
WährungIDRUSD (fest codiert)
FX_RATE_UNAVAILABLENeuer 503 + Retry-After, wenn kein verwendbarer Kurs vorliegt
PräzisionSummen 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

GET/balance
v1
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
v2
{
  "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" } }
}
GET/catalog/products
v1
{
  "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 }
}
v2
{
  "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.

POST/orders/cancel
v1
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
v2
{
  "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

  1. Wechseln Sie den Basispfad /v1/v2.
  2. Parsen Sie Geldfelder als Objekte — lesen Sie amount als Dezimal-String; currency ist "USD".
  3. Verwenden Sie für die Ledger-Abstimmung canonical_amount (exaktes IDR); der USD-amount ist eine Projektion zur Renderzeit, und der rate wird einmal in meta.fx offengelegt.
  4. Behandeln Sie den neuen FX_RATE_UNAVAILABLE (503) — wiederholen Sie die Anfrage nach Retry-After. v1 gibt dies niemals zurück.