API-Dokumentation
Öffentliche API /v1 – authentifiziere dich mit einem API-Schlüssel und automatisiere Postfachmieten aus deiner eigenen Software
Dein API-Schlüssel
Du bist nicht angemeldet – melde dich an, um deinen eigenen API-Schlüssel zu erhalten.
Sende diesen Schlüssel im Header Authorization: Bearer <key> oder im Query-Parameter ?api_key=<key>. Klicke auf das Augen-Symbol, um den ganzen Schlüssel anzuzeigen; klicke auf Neuen Schlüssel erzeugen, wenn du ihn ersetzen musst (der alte Schlüssel funktioniert sofort nicht mehr).Überblick
Basis-URL
https://otpgmail.netAuthentifizierung
Füge deinen API-Schlüssel jeder Anfrage hinzu, entweder im Header
Authorization: Bearer og_xxx... (empfohlen) oder im Query-String ?api_key=og_xxx....Idempotency-Key (verhindert doppelte Bestellungen)
Jede Anfrage
POST /v1/orders sollte den Header Idempotency-Key: <neue UUID pro Bestellung> enthalten. Läuft die Anfrage in ein Timeout oder bricht die Verbindung ab, sende sie erneut mit dem gleichen Schlüssel – der Server liefert dann die ursprüngliche Antwort zurück, statt eine weitere Bestellung anzulegen. Erzeuge für jede neue Bestellung eine neue UUID und verwende eine UUID nur bei einem erneuten Versuch wieder.Viele Bestellungen auf einmal: Status in einem Aufruf
Du mietest Dutzende oder Hunderte Postfächer? Frage ihren Status gemeinsam ab:
POST /v1/orders/status liefert bis zu 100 Bestellungen pro Aufruf (zählt als eine einzige Anfrage für dein Rate-Limit), und GET /v1/orders listet deine Bestellungen – nach einem Neustart liefert ?status=waiting_code alle Bestellungen zurück, die noch auf einen Code warten. Jeder Fehler sagt außerdem, ob sich ein erneuter Versuch lohnt (error.retryable, error.retryAfterSec). Details: Abschnitt Status vieler Bestellungen.Postfachtyp: Gmail oder iCloud
Standardmäßig erhältst du ein
@gmail.com-Postfach. Um ein @icloud.com-Postfach zu mieten, füge "domain": "icloud.com" zum Body von POST /v1/orders hinzu. Ohne domain gilt gmail.com – bestehende Integrationen funktionieren ohne Änderungen weiter. iCloud ist nur für Dienste verfügbar, deren Feld icloud in GET /v1/services nicht null ist; der iCloud-Preis kann vom Gmail-Preis abweichen (siehe icloud.price). Jede Bestellantwort enthält ein Feld domain, das angibt, welchen Postfachtyp die Bestellung nutzt. Einige Dienste bieten auch @gmx.com-Postfächer an (derzeit nur Pokemon, Code pkm, Lager 2): Sende "domain": "gmx.com" – ein Dienst bietet es an, wenn sein offers[] in GET /v1/services einen Eintrag mit domain = "gmx.com" hat; bei jedem anderen Dienst ⇒ DOMAIN_UNAVAILABLE. gmx.com ist für Sammelbestellungen nicht verfügbar.Lager: ein Dienst, mehrere Postfachquellen
Ein Dienst kann aus mehreren Lagern verkauft werden, jedes mit eigenem Preis, Bestand und eigenen Coderegeln (das Array
offers[] in GET /v1/services). Sende beim Anlegen einer Bestellung "warehouse": 2, um eines zu wählen; "warehouse": 99 ⇒ das System wählt das günstigste Lager mit Bestand und geht zum nächsten über, wenn es leer ist (ideal für Dienste, die oft ausverkauft sind, wie Facebook und Instagram); 0 ⇒ das günstigste Lager mit Bestand. Bestehende Clients brauchen keine Änderungen: ohne warehouse ⇒ wie bisher Lager 1, und price, stock und icloud beschreiben Lager 1 (Dienste ohne Lager 1 ⇒ das günstigste Lager mit Bestand). Details: siehe Lager.Sammelmiete (Sammelbestellungen)
Sammelbestellungen werden im Web gekauft (Seite Sammelmiete); über die API kannst du Sammelbestellungen auflisten und Codes lesen. Gmail (Lager 1): Jedes Postfach hat eine
proxyUrl – einen Code-Link, der keinen API-Schlüssel braucht. iCloud (Lager 3): private Postfächer; lies Codes mit GET /v1/batches/{id}/mails/{mailId}/codes oder öffne readUrl im Web. Details: Abschnitt Sammelmiete.Kurzlebige Gmail-Konten kaufen (Mail-Shop)
Neben dem Mieten von Postfächern kannst du auch komplette Gmail-Konten kaufen – aus 2 Lagern: Lager 1 liefert E-Mail, Passwort, Cookies + Postfach-Link (auch als JSON), Lager 2 E-Mail, Passwort + schnellen Login-Link; Lager 99 nimmt zuerst das günstigste (die Seite Kurzzeit-Gmail kaufen im Web). Die API kann alles, was diese Seite kann: Lager, Preise und Bestand ansehen, kaufen (
Idempotency-Key erforderlich), die Lieferung verfolgen, die Konten herunterladen und ihre E-Mails lesen. Details: Abschnitt Mail-Shop.Deine Kunden Codes selbst lesen lassen (die Seite /otp)
Du verkaufst gemietete Postfächer weiter (im Web oder über die API)? Schalte auf deiner Kontoseite einen Teilen-Code ein und schick deinem Kunden dann den Link
https://otpgmail.net/de/otp#k7m2q9:[email protected] (k7m2q9 = dein Teilen-Code) – er sieht die neuesten Codes dieser E-Mail ohne Anmeldung und sieht nie Preise oder dein Guthaben; die Seite führt nur kostenlose Aktionen aus (sie berechnet dir nie etwas).JSON-Link für Tools / Bots (deine oder die deines Kunden): Nimm den Link in Pfadform
https://otpgmail.net/de/otp/k7m2q9:[email protected] (er öffnet die Seite /otp genau wie der #-Link) und hänge ?json an ⇒ der Server liefert JSON mit genau den Daten, die die Seite zeigt: cards[] – eine Karte pro Dienst, der auf dieser E-Mail gemietet ist (email, serviceName, state, items[] = bis zu 5 neueste Codes / Links, neueste zuerst) – plus serverTime. state: waiting wartet auf den ersten Code · listening wartet auf einen neuen Code · received Codes sind da · needs_owner der Verkäufer muss verlängern · expired abgelaufen · banned Postfach gesperrt. Mehrere E-Mails: durch Kommas trennen (höchstens 50). Kein API-Schlüssel, keine Cookies, CORS ist offen; nur lesen („Neuen OTP laden“ / „Diese E-Mail erneut mieten“ bleiben auf der Seite /otp). Dieselben Schutzgrenzen gegen Raten wie auf der Seite: falscher Code / keine anzeigbare Bestellung für die E-Mail ⇒ 404 NOT_FOUND; wiederholte Fehlversuche oder mehr als 30 Abrufe pro Minute für eine E-Mail ⇒ 429 RATE_LIMITED mit retryAfter (Sekunden). ?json an einen /otp#…-Link anzuhängen liefert KEIN JSON: Browser schicken den Teil nach # nie an den Server.curl "https://otpgmail.net/de/otp/k7m2q9:[email protected]?json"{
"success": true,
"data": {
"cards": [
{
"ref": "Q3vX9kL2mP0aB7cD",
"serviceCode": "fb",
"serviceName": "Facebook",
"logo": "/logos/fb.png?v=20261007",
"domain": "gmail.com",
"email": "[email protected]",
"state": "received",
"until": null,
"items": [
{
"kind": "code",
"value": "529170",
"label": null,
"at": 1760000405
},
{
"kind": "code",
"value": "381904",
"label": null,
"at": 1759999030
}
],
"total": 2,
"canLoad": true
}
],
"serverTime": 1760000530
}
}Vollständiger Beispielcode auf GitHub
Sofort lauffähige Clients für Python, Node.js, PHP, C# (.NET) und curl – Postfach mieten, auf den Code warten, für eine Erstattung automatisch stornieren und zwischen iCloud und Gmail wechseln, wenn der Bestand ausgeht: github.com/letoan0902/otpgmail-email-otp-api (Englisch) · thue-otp-gmail (Vietnamesisch). Hinweis zu Python: Nutze
requests statt des einfachen urllib (dessen Standard-User-Agent bekommt vom CDN ein 403).Webhooks
Statt jede Bestellung ständig abzufragen, registrieren Sie eine URL und erhalten einen signierten POST, sobald eine Bestellung einen neuen Code erhält oder storniert / erstattet wird — siehe Webhooks.
Endpunkte
GET/v1/balance
Gibt das Kontoguthaben (in VND) für den in der Anfrage verwendeten API-Schlüssel zurück.
Beispielcurl
Python
Node.js
curl "https://otpgmail.net/v1/balance" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"balance": 25000
}
}Mögliche Fehler: UNAUTHORIZEDGET/v1/services
Listet die aktivierten Dienste auf – Code, Name, Gmail-Preis (in VND) und aktuellen Gmail-Bestand, einen icloud-Block mit Preis und Bestand für @icloud.com-Postfächer sowie ein Array offers[]: jedes Lager, das den Dienst verkauft, ein Eintrag pro Lager × Postfachtyp (Preis, Bestand, Anzahl der Codes, Regeln). Die Daten sind zwischengespeichert: Der Bestand wird etwa alle 30–60 Sekunden aktualisiert, Preise seltener.
icloud = null bedeutet, dass der Dienst iCloud noch nicht unterstützt (der Anbieter bietet es nicht an oder es ist vorübergehend deaktiviert) – es kann nur Gmail gemietet werden. Bei Diensten mit einem icloud-Block sendest du beim Anlegen der Bestellung "domain": "icloud.com", um ein @icloud.com-Postfach zu icloud.price zu erhalten. price / stock / icloud.price beschreiben Lager 1 – dorthin gehen Bestellungen ohne warehouse (Dienste ohne Lager 1 ⇒ das günstigste Lager mit Bestand); Preise und Bestand pro Lager stehen in offers[] (siehe Lager). stock ist die Zahl, die die Quelle veröffentlicht (Lager 1: etwa alle 30 Sekunden aktualisiert), kein Versprechen, dass ein einzelner Aufruf so viele mieten kann: Postfächer werden einzeln zugewiesen, und die Quelle kann mittendrin leerlaufen – dann liefert POST /v1/orders weniger Bestellungen plus ein Feld partial. Lager 2 und 4 zeigen ebenso die Zahlen ihrer Quellen.
Beispielcurl
Python
Node.js
curl "https://otpgmail.net/v1/services" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": [
{
"code": "dr",
"name": "OpenAI (ChatGPT)",
"price": 350,
"stock": 616525,
"icloud": {
"price": 630,
"stock": 12464
},
"offers": [
{
"warehouse": 2,
"domain": "gmail.com",
"price": 350,
"stock": 616525,
"maxCodes": null,
"rules": null,
"fee": {
"freeHours": 6,
"paidPct": 50,
"maxHours": 24
},
"isDefault": true
},
{
"warehouse": 1,
"domain": "gmail.com",
"price": 630,
"stock": 12464,
"maxCodes": 3,
"rules": null,
"fee": null,
"isDefault": false
},
{
"warehouse": 1,
"domain": "icloud.com",
"price": 630,
"stock": 12464,
"maxCodes": 5,
"rules": null,
"fee": null,
"isDefault": true
}
],
"note": null
},
{
"code": "fb",
"name": "Facebook",
"price": 1590,
"stock": 88,
"icloud": null,
"offers": [
{
"warehouse": 1,
"domain": "gmail.com",
"price": 1590,
"stock": 88,
"maxCodes": 10,
"rules": null,
"fee": null,
"isDefault": false
},
{
"warehouse": 4,
"domain": "gmail.com",
"price": 1580,
"stock": 2310,
"maxCodes": null,
"rules": null,
"fee": null,
"isDefault": true
}
],
"note": null
}
]
}Mögliche Fehler: UNAUTHORIZEDPOST/v1/orders
Legt eine Bestellung für ein Postfach an, das Bestätigungscodes empfängt – standardmäßig Gmail, oder iCloud, wenn du domain: "icloud.com" sendest. Dein Guthaben wird belastet, sobald der Anbieter das Postfach zuweist. Gibt eine Liste von Bestellungen zurück (eine, oder mehrere bei quantity > 1).
Teillieferung: quantity ist eine Obergrenze
- Postfächer werden einzeln nacheinander zugewiesen. Gehen dem Anbieter mittendrin die Postfächer aus (z. B. hat Lager 1 in diesem Moment keine Postfächer mehr für diesen Dienst), stoppt der Aufruf und liefert HTTP 200 mit den bis dahin erstellten Bestellungen –
datakann kürzer als quantity sein, und berechnet werden nur die Bestellungen indata. DerstockinGET /v1/servicesgarantiert nicht, wie viele ein einzelner Aufruf bekommt. - Die Antwort enthält dann zusätzlich ein Feld
partialauf oberster Ebene nebendata(fehlt, wenn alles geliefert wurde – Bots, die nurdatalesen, laufen unverändert weiter):requested,delivered,missingundreason={code, message, retryable, retryAfterSec};codeist der Fehler, der den Aufruf gestoppt hat (NO_MAILS_AVAILABLE,OUT_OF_STOCK,ALL_WAREHOUSES_EXHAUSTED,INSUFFICIENT_BALANCE,WAITING_LIMIT_REACHED…). - Leerlaufen ist meist nur vorübergehend – bei Lager 1 hat der Anbieter oft schon wenige Sekunden später wieder Postfächer. Bei
reason.retryable= true wartereason.retryAfterSecSekunden (3–5 s) und fordere die fehlenden Postfächer (missing) mit einem NEUENIdempotency-Keyerneut an (der alte Schlüssel liefert nur genau diese Antwort noch einmal); wiederhole das, bis du alle hast. - Direkt nach einer Teillieferung kann der nächste Aufruf 409
WAREHOUSE_UNAVAILABLE/OUT_OF_STOCKoder 503NO_MAILS_AVAILABLEliefern – das ist normal: halte weiter 3–5 s Pause zwischen den Aufrufen und gib erst auf, wenn viele Aufrufe hintereinander scheitern (z. B. 10 Versuche, etwa eine Minute); versuche es dann später erneut oder sende"warehouse": 99, damit das System auf andere Lager ausweicht.retryable= false (INSUFFICIENT_BALANCE,WAITING_LIMIT_REACHED…) ⇒ behebe zuerst die Ursache (Guthaben aufladen oder warten, bis offene Bestellungen abgeschlossen sind). - Fehlerantworten enthalten dieselben zwei Felder in
error:error.retryableunderror.retryAfterSec(z. B. 409OUT_OF_STOCK⇒true, 5; 402INSUFFICIENT_BALANCE⇒false,null) – siehe Fehlercodes.
Der Header Idempotency-Key (eine UUID) ist erforderlich. Erzeuge für jede neue Miete eine neue UUID; bei einem erneuten Versuch nach einem Timeout verwende DENSELBEN Schlüssel erneut, um keine doppelte Bestellung anzulegen. Dieser Aufruf wartet, bis der Anbieter ein Postfach zuweist, dauert also meist etwa 0,3 Sekunden, kann zu Spitzenzeiten (selten) aber bis zu 10 Sekunden dauern – stelle das Timeout deines Clients auf etwa 15 Sekunden ein, statt nach 5 Sekunden aufzugeben und zu früh erneut zu versuchen.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
service | string | Ja | Dienstcode (aus GET /v1/services, z. B. "ig", "tg", "fb") |
quantity | number | Nein | HÖCHSTZAHL der zu mietenden Postfächer (Standard 1; höchstens 200 pro Anfrage) – geht das Lager mittendrin aus, bekommst du weniger: zähle data[] und lies partial |
domain | string | Nein | Postfachtyp: "gmail.com" (Standard), "icloud.com" oder "gmx.com". Ohne Angabe = gmail.com – bestehende Integrationen brauchen keine Änderungen. iCloud kann nur für Dienste gemietet werden, deren Feld icloud in GET /v1/services nicht null ist; "gmx.com" nur für Dienste mit offers[].domain = "gmx.com" (derzeit nur Pokemon, Code pkm) – andernfalls schlägt der Aufruf mit DOMAIN_UNAVAILABLE fehl. Jeder andere Wert ⇒ VALIDATION_ERROR. |
warehouse | number | Nein | Lager, aus dem gemietet wird. Ohne Angabe = LAGER 1, genau wie vor der Einführung mehrerer Lager (Dienste ohne Lager 1 weichen auf das günstigste aus) – bestehende Clients brauchen keine Änderungen. 0 = das günstigste Lager mit Bestand (es wird nur ein Lager versucht). 1–4 = genau dieses Lager (aus offers[].warehouse in GET /v1/services); wenn es diesen Dienst/Postfachtyp nicht verkauft, pausiert oder leer ist ⇒ WAREHOUSE_UNAVAILABLE. 99 = AUTOMATISCH: Lager mit Bestand werden vom günstigsten zum teuersten versucht und es geht weiter, wenn eines leer ist oder ausfällt; bleibt keines übrig ⇒ ALL_WAREHOUSES_EXHAUSTED – empfohlen für Dienste, die oft ausverkauft sind (Facebook, Instagram …). Siehe Lager. |
curl
Python
Node.js
# Erzeuge für jede NEUE Bestellung einen zufälligen Idempotency-Key
# Verwende bei einem erneuten Versuch nach einem Timeout DENSELBEN Schlüssel erneut (verhindert doppelte Bestellungen)
# domain: "gmail.com" (Standard, optional) | "icloud.com" (ein @icloud.com-Postfach mieten) | "gmx.com" (nur Dienste, die es anbieten, z. B. Pokemon)
# warehouse: 0 | 1–4 | 99 (optional – ohne Angabe bleibt es wie bisher bei Lager 1; 0 = günstigstes Lager mit Bestand; 99 = automatisch, geht weiter, wenn ein Lager leer ist; siehe offers[] in GET /v1/services)
curl -X POST "https://otpgmail.net/v1/orders" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"service":"ig","quantity":1,"domain":"gmail.com"}'{
"success": true,
"data": [
{
"orderId": "abc123xyz",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "waiting_code",
"price": 1060,
"otp": [],
"codeDeadlineAt": 1753270000,
"rentExpiresAt": 1753356400,
"createdAt": 1753268200,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
}
]
}Beispielantwort – Teillieferung (quantity 5, 2 Postfächer geliefert)
Mögliche Fehler: {
"success": true,
"data": [
{
"orderId": "abc123xyz",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "waiting_code",
"price": 1060,
"otp": [],
"codeDeadlineAt": 1753270000,
"rentExpiresAt": 1753356400,
"createdAt": 1753268200,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
},
{
"orderId": "jkl012mno",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "waiting_code",
"price": 1060,
"otp": [],
"codeDeadlineAt": 1753270001,
"rentExpiresAt": 1753356401,
"createdAt": 1753268201,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
}
],
"partial": {
"requested": 5,
"delivered": 2,
"missing": 3,
"reason": {
"code": "NO_MAILS_AVAILABLE",
"message": "No inboxes available for this service right now, please try again later",
"retryable": true,
"retryAfterSec": 5
}
}
}UNAUTHORIZED, VALIDATION_ERROR, SERVICE_DISABLED, INSUFFICIENT_BALANCE, OUT_OF_STOCK, DOMAIN_UNAVAILABLE, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED, NO_MAILS_AVAILABLE, QUANTITY_EXCEEDED, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCEGET/v1/orders/{id}
Gibt eine Bestellung anhand der ID zurück, einschließlich der bisher empfangenen Codes (otp[]). Frage diesen Endpunkt alle 3–5 Sekunden ab, um den Code abzuholen, solange die Bestellung den Status waiting_code hat.
Das Array otp[] ist nach Eingang sortiert: ältester zuerst, NEUESTER ZULETZT – lies den neuesten Code mit otp[otp.length - 1]. Bei jedem Aufruf prüft das System beim Anbieter noch offene Bestellungen, im eigenen Takt jedes Lagers (alle paar Sekunden) – häufigeres Aufrufen liefert nur die gespeicherten Daten. Frage nach dem ersten Code weiter ab: Weitere Codes (wenn der Dienst mehr als einen sendet) werden ohne Aufpreis innerhalb des Bestellzeitraums ans Ende von otp[] angehängt – das Limit und das Zeitfenster werden vor dem Mieten angezeigt (maxCodes und fee in offers[]); um sicherzustellen, dass das System auf einen neuen Code wartet, rufe POST /v1/orders/{id}/next-code auf (jedes Lager; nur Lager 2 berechnet außerhalb seines kostenlosen Zeitraums eine Gebühr). Erlaubt der Dienst nur einen Code pro Postfach, endet otp[] bei diesem Code – die Antworten bleiben normal, ohne Fehler. Das Feld warehouse sagt dir, welches Lager das Postfach geliefert hat (1–4); nextCodeFee ist die Gebühr für weitere Codes (Bestellungen aus Lager 2 und 3; null für die Lager 1 und 4) – siehe Lager. Seit 2026-10-02 enthalten Bestellungen außerdem nextCodeBlocked (true = das Postfach kann keine weiteren Codes mehr empfangen) und nextCodeBlockReason – den Grund: "email_banned" (die E-Mail wurde vom Dienst gesperrt – miete eine neue), "activation_gone" (der Anbieter hat das Postfach geschlossen), "hard_expired" (das Code-Fenster ist beendet), "max_codes" (die maximale Anzahl an Codes wurde empfangen), null (nicht blockiert / ältere Bestellung ohne erfassten Grund). Das sind zusätzliche Felder – bestehende Clients brauchen keine Änderungen. Du fragst viele Bestellungen ab? Sende bis zu 100 IDs pro Aufruf an POST /v1/orders/status statt eines GET pro Bestellung – dieselben Bestellobjekte, nur eine Anfrage für dein Rate-Limit.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Bestell-ID (die orderId, die beim Anlegen der Bestellung zurückgegeben wurde) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/orders/ORDER_ID" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"orderId": "abc123xyz",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "completed",
"price": 1060,
"otp": [
{
"code": "123456",
"receivedAt": 1753268500
},
{
"code": "789012",
"receivedAt": 1753269000
}
],
"codeDeadlineAt": 1753270000,
"rentExpiresAt": 1753356400,
"createdAt": 1753268200,
"warehouse": 1,
"nextCodeFee": null,
"listening": true,
"listenUntil": 1753270500,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUNDPOST/v1/orders/{id}/cancel
Storniert die Bestellung und erstattet 100 % des Preises. Nur erlaubt, solange die Bestellung den Status waiting_code hat UND noch kein Code empfangen wurde.
Eine Bestellung, die einen Code erhalten hat (auch nur einen), kann nicht storniert oder erstattet werden – das gilt für die Website und die API gleichermaßen. Bestellungen mit rules.cancellable = false (Dienste aus eigenem Bestand wie Cointiply) können nicht storniert werden: Sobald das Postfach zugewiesen ist, gibt es keine Stornierung und keine Erstattung – der Stornoaufruf liefert ORDER_NOT_CANCELLABLE. Bestellungen aus Lager 3 bei regulären Diensten (rules.cancellable = true) können wie jede andere Bestellung vor dem Eintreffen eines Codes storniert werden.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | ID der zu stornierenden Bestellung |
curl
Python
Node.js
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/cancel" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json"{
"success": true,
"data": {
"orderId": "abc123xyz",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "cancelled",
"price": 1060,
"otp": [],
"codeDeadlineAt": 1753270000,
"rentExpiresAt": 1753356400,
"createdAt": 1753268200,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, ORDER_NOT_CANCELLABLE, PROVIDER_ERRORPOST/v1/orders/{id}/rerent
Mietet dasselbe Postfach erneut für eine Bestellung eines Dienstes aus eigenem Bestand (provider = "inventory", z. B. Cointiply): legt eine NEUE Bestellung auf diesem Postfach mit einem frischen Empfangsfenster an, zum rerentPriceVnd der alten Bestellung.
Funktioniert nur, wenn die alte Bestellung canRerent = true hat (die neueste Bestellung dieses Postfachs, deren vorheriges Fenster beendet ist oder alle E-Mails empfangen hat). Du wirst belastet, sobald es gelingt – sende einen Idempotency-Key und verwende ihn bei einem erneuten Versuch wieder, damit dir nie doppelt berechnet wird. Die Antwort ist { order, message }; order hat dieselbe Form wie GET /v1/orders/{id}. Lager 2 (seit 2026-10-09): eine Bestellung, die storniert wurde, in ein Timeout lief oder abgelaufen ist (Postfach geschlossen) – rufe es auf, wenn
ParametercanRerent = true ist (die neueste Bestellung dieser E-Mail, und die E-Mail hat keine andere aktive Bestellung). Das System öffnet genau diese E-Mail als NEUE Bestellung (status = waiting_code) zum Preis rerentPriceVnd (derzeit 100 % deines Preises) und wartet wie bei jeder Bestellung aus Lager 2 20 Minuten auf eine E-Mail – kommt keine, wird sie storniert und zu 100 % erstattet. E-Mail vom Dienst gesperrt ⇒ RERENT_EMAIL_BANNED; der Anbieter kann sie nicht mehr öffnen ⇒ RERENT_MAIL_GONE; Lager 2 vorübergehend ausgefallen ⇒ PROVIDER_ERROR – bei keinem der drei wird dir etwas berechnet.| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | ID der vorherigen Bestellung des Postfachs (die mit canRerent = true) |
curl
Python
Node.js
# Verwende bei einem erneuten Versuch nach einem Timeout DENSELBEN Schlüssel erneut (verhindert doppelte Bestellungen)
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/rerent" \
-H "Authorization: Bearer og_xxx..." \
-H "Idempotency-Key: $(uuidgen)"{
"success": true,
"data": {
"order": {
"orderId": "def456uvw",
"service": "ctp",
"domain": "icloud.com",
"email": "[email protected]",
"status": "waiting_code",
"price": 3500,
"otp": [],
"codeDeadlineAt": 1753273500,
"rentExpiresAt": 1753273500,
"createdAt": 1753269000,
"provider": "inventory",
"rules": {
"windowMinutes": 75,
"maxCodes": 4,
"cancellable": false,
"refundOnTimeout": false,
"rerentRatio": 0.5
},
"windowEndsAt": 1753273500,
"canRerent": false,
"rerentPriceVnd": 3500,
"warehouse": 3,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
},
"message": "Inbox rented again — a new 75-minute message window has started"
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, RERENT_NOT_AVAILABLE, RERENT_EMAIL_BANNED, RERENT_MAIL_GONE, INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCEPOST/v1/orders/{id}/next-code
Fordert den nächsten Code auf demselben Postfach an – funktioniert für Bestellungen aus JEDEM Lager (Lager 1–4), die ihren ersten Code bereits erhalten haben (status = completed). Kostenlos, außer bei Bestellungen aus Lager 2, deren kostenloser Zeitraum beendet ist (nextCodeFee.feePriceVnd > 0): Dann sende confirmFee: true, um die Gebühr zu zahlen und ein weiteres Code-Fenster zu öffnen (bis zur harten Grenze nextCodeFee.hardUntil). Dein Bot muss das Lager nicht kennen: Frage GET /v1/orders/{id} weiter ab – neue Codes werden an otp[] angehängt – und rufe diesen Endpunkt auf, wenn das System auf einen neuen Code warten soll.
Was dieser Aufruf in jedem Lager tut
- Lager 1: Das System wartet nach jedem Code innerhalb des Bestellzeitraums (
rentExpiresAt) selbst auf den nächsten Code – der Aufruf liefert 200 und ändert nichts; kann das Postfach keine Codes mehr aufnehmen (Code-Limit erreicht, Postfach geschlossen, abgelaufen) ⇒ 409 mitnextCodeBlockReason. - Lager 2: öffnet das Postfach beim Anbieter erneut (
reordered= true) und wartet 20 Minuten auf eine E-Mail; innerhalb des kostenlosen Zeitraums kostenlos (nextCodeFee.feePriceVnd= 0), außerhalb musst duconfirmFee= true senden (fehlt es ⇒ 409NEXT_CODE_FEE_CONFIRM, nichts berechnet). Die Gebühr bleibt nur bestehen, wenn ein neuer Code ankommt – kein Code innerhalb von 20 Minuten ⇒ automatische Erstattung. Nach jedem Code wartet das System weitere 25 Minuten auf den nächsten (listening= true,listenUntil). - Lager 3 (reguläre Dienste): öffnet ein kurzes Abhörfenster, kostenlos innerhalb des kostenlosen Zeitraums (
nextCodeFee.freeUntil); danach ⇒ 409 – nutzePOST /v1/orders/{id}/rerent. Dienste aus eigenem Bestand wie Cointiply (rules.cancellable= false) haben keinen nächsten Code (409): Jede E-Mail erscheint während des Fensters von selbst. - Lager 4: liest die Bestellung beim Anbieter neu und wartet auf einen neuen Code, kostenlos bis
rentExpiresAt.
200 = das System wartet auf einen neuen Code für diese Bestellung (listening = true, listenUntil = wann es aufhört; reordered = true, wenn das Postfach gerade erneut geöffnet wurde; chargedVnd, wenn eine Gebühr erhoben wurde), oder genau beim Aufruf ist ein neuer Code angekommen (bereits in order.otp). 409 = es konnte kein neues Wartefenster geöffnet werden – lies error.code; warum das Postfach keine Codes mehr aufnimmt, steht in order.nextCodeBlockReason (GET /v1/orders/{id}). Eine Bestellung, die noch auf ihren ersten Code wartet (waiting_code) ⇒ 409, du musst noch nicht aufrufen. Sende bei kostenpflichtigen Aufrufen einen Idempotency-Key (verwende ihn bei einem erneuten Versuch wieder, damit dir nie doppelt berechnet wird). Anbieter nicht erreichbar ⇒ 502 PROVIDER_ERROR, versuche es später erneut. 409-Fehlercodes dieses Aufrufs: NEXT_CODE_FEE_CONFIRM (Lager 2 verlangt die Gebühr – rufe erneut mit confirmFee: true auf), EMAIL_BANNED (die E-Mail wurde vom Dienst gesperrt – miete eine neue; error.details.blockReason = "email_banned"), ORDER_EXPIRED (abgelaufen, Postfach geschlossen, Code-Limit erreicht oder der Dienst hat keinen „nächsten Code“ – der Grund steht in order.nextCodeBlockReason), ORDER_NOT_CANCELLABLE (die Bestellung hat ihren ersten Code noch nicht erhalten). Bots, die jedes 409 dieses Aufrufs als „keine Codes mehr“ behandeln, funktionieren unverändert weiter. refresh: true (Lager 2): prüft zuerst auf eine neue E-Mail (eine gerade eingetroffene wird zugestellt, kein Neuöffnen), öffnet dann das Postfach beim Anbieter neu – im kostenlosen Zeitraum beginnt die Wartezeit erneut für 20 Minuten; außerhalb behält sie ihr Ende (listenUntil) und es wird nichts berechnet. Die Antwort enthält zusätzlich refreshed (true = gerade neu geöffnet) und refreshAvailableAt (Unix-Sekunden; davor prüft ein weiteres Erneuern nur auf E-Mails; null = sofort möglich). Andere Lager: keine Wirkung (refreshed = false). Ohne refresh verhält sich der Aufruf genau wie bisher.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Bestell-ID (orderId) – jedes Lager |
confirmFee | boolean | Nein | true = du bist einverstanden, nextCodeFee.feePriceVnd zu zahlen, um ein weiteres Code-Fenster zu öffnen. Nicht nötig, solange feePriceVnd = 0. |
refresh | boolean | Nein | true = Lager 2, die Bestellung wartet auf einen neuen Code (listening = true), aber es kommt keiner: das Postfach beim Anbieter sofort neu öffnen (Wartezeit erneuern) – die alte Wartezeit nimmt beim Anbieter eventuell keine E-Mails mehr an. Kostenlos; höchstens einmal alle 5 s pro Bestellung. Wartet die Bestellung nicht ⇒ wie ein normaler Aufruf. |
curl
Python
Node.js
# Kostenlos (nextCodeFee ist null oder feePriceVnd = 0 – jedes Lager außer Lager 2 nach seinem kostenlosen Zeitraum): kein confirmFee nötig
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
-H "Authorization: Bearer og_xxx..."
# Lager 2 nach seinem kostenlosen Zeitraum: sende confirmFee = true, um nextCodeFee.feePriceVnd zu zahlen
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"confirmFee":true}'{
"success": true,
"data": {
"order": {
"orderId": "ghi789rst",
"service": "dr",
"domain": "gmail.com",
"email": "[email protected]",
"status": "completed",
"price": 350,
"otp": [
{
"code": "482913",
"receivedAt": 1753268300
}
],
"codeDeadlineAt": 1753269700,
"rentExpiresAt": 1753354200,
"createdAt": 1753267800,
"warehouse": 2,
"nextCodeFee": {
"feePriceVnd": 0,
"freeUntil": 1753311000,
"hardUntil": 1753354200,
"paidPct": 50,
"freeHours": 6
},
"listening": true,
"listenUntil": 1753269500,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
},
"message": "Another code window is open — waiting for a new code",
"listening": true,
"listenUntil": 1753269500,
"reordered": true
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, NEXT_CODE_FEE_CONFIRM, INSUFFICIENT_BALANCE, EMAIL_BANNED, ORDER_EXPIRED, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR, DUPLICATE_REQUEST, MAINTENANCEStatus vieler Bestellungen in einem Aufruf
Wiederverkäufer und Bots, die viele Postfächer mieten, sollten nicht für jede Bestellung ein eigenes
GET /v1/orders/{id} senden: POST /v1/orders/status liest bis zu 100 Bestellungen pro Aufruf mit denselben Bestellobjekten, und GET /v1/orders listet deine Bestellungen aus den gespeicherten Daten.- Frage alle 3–5 Sekunden mit
POST /v1/orders/statusab, bis zu 100 Bestell-IDs pro Aufruf – jeder Aufruf zählt als eine Anfrage für dein Rate-Limit. - Pro Aufruf prüft das System beim Anbieter höchstens 20 offene Bestellungen der Liste (zuerst die, die auf ihren ersten Code warten, dann die am längsten nicht geprüften) und liefert den Rest aus den gespeicherten Daten; diese werden bei den nächsten Aufrufen geprüft. Ein Aufruf antwortet in etwa 3 Sekunden.
- Nimm eine Bestellung aus deiner Liste, sobald sie abgeschlossen ist: cancelled (erstattet) oder completed, wenn du keine weiteren Codes brauchst.
- IDs, die es nicht gibt oder die zu einem anderen Konto gehören, kommen in
missingzurück (kein 404) – nimm sie ebenfalls heraus. - Hole dir nach einem Neustart alle offenen Bestellungen mit
GET /v1/orders?status=waiting_codezurück (folgenextCursor) und frage dann weiter mitPOST /v1/orders/statusab.
POST/v1/orders/status
Liefert bis zu 100 deiner Bestellungen in einem Aufruf. Jedes Element ist genau das, was GET /v1/orders/{id} für diese Bestellung zurückgibt (status, otp[] mit allen empfangenen Codes, warehouse …). Damit fragst du viele offene Bestellungen gleichzeitig ab.
data behält die Reihenfolge von ids bei; doppelte IDs werden ignoriert. IDs, die es nicht gibt oder die zu einem anderen Konto gehören, stehen in missing (kein 404). Bei offenen Bestellungen prüft das System beim Anbieter wie GET /v1/orders/{id}, aber höchstens für 20 Bestellungen pro Aufruf – zuerst die, die auf ihren ersten Code warten, dann die am längsten nicht geprüften – und liefert die übrigen aus den gespeicherten Daten (sie werden bei den nächsten Aufrufen geprüft); ein Aufruf antwortet in etwa 3 Sekunden. Ein Aufruf zählt als eine Anfrage für dein Rate-Limit, egal wie viele IDs er enthält. Mehr als 100 IDs, eine leere Liste oder IDs, die keine Strings sind ⇒ 400 VALIDATION_ERROR.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
ids | string[] | Ja | Bestell-IDs (orderId) – 1 bis 100 pro Aufruf |
curl
Python
Node.js
# IDs der Bestellungen, auf die du wartest (orderId aus POST /v1/orders) – bis zu 100 pro Aufruf
curl -X POST "https://otpgmail.net/v1/orders/status" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-d '{"ids":["ORDER_ID_1","ORDER_ID_2"]}'{
"success": true,
"data": [
{
"orderId": "abc123xyz",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "completed",
"price": 1060,
"otp": [
{
"code": "123456",
"receivedAt": 1753268500
}
],
"codeDeadlineAt": 1753270000,
"rentExpiresAt": 1753356400,
"createdAt": 1753268200,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
},
{
"orderId": "jkl012mno",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "waiting_code",
"price": 1060,
"otp": [],
"codeDeadlineAt": 1753270001,
"rentExpiresAt": 1753356401,
"createdAt": 1753268201,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
}
],
"missing": [
"zzz999qqq"
]
}Mögliche Fehler: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITEDGET/v1/orders
Listet deine Bestellungen, neueste zuerst, nur aus gespeicherten Daten – es fragt den Anbieter nicht nach neuen Codes (das macht POST /v1/orders/status). Filtere nach Status und Erstellungszeit; blättere mit nextCursor.
Übergib nextCursor als cursor, um die nächste Seite zu bekommen; nextCursor = null ⇒ letzte Seite. Seiten wiederholen oder überspringen nie eine Bestellung, selbst wenn viele Bestellungen in derselben Sekunde angelegt wurden. Interne Zustände werden nie gelistet (nur waiting_code, completed, cancelled und queued). Nach einem Neustart deines Bots: GET /v1/orders?status=waiting_code&limit=200, folge nextCursor und frage diese Bestellungen dann mit POST /v1/orders/status ab.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
status | string | Nein | Nur dieser Status: waiting_code | completed | cancelled | queued (weggelassen ⇒ alle) |
since | number | Nein | Nur Bestellungen, die zu oder nach dieser Unix-Zeit (Sekunden) angelegt wurden |
limit | number | Nein | Bestellungen pro Seite: 1–200 (Standard 50) |
cursor | string | Nein | nextCursor der vorigen Seite, unverändert |
curl
Python
Node.js
# Nach einem Neustart: alle Bestellungen, die noch auf einen Code warten, Seite für Seite einsammeln
curl "https://otpgmail.net/v1/orders?status=waiting_code&limit=200" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": [
{
"orderId": "jkl012mno",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "waiting_code",
"price": 1060,
"otp": [],
"codeDeadlineAt": 1753270001,
"rentExpiresAt": 1753356401,
"createdAt": 1753268201,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
},
{
"orderId": "abc123xyz",
"service": "ig",
"domain": "gmail.com",
"email": "[email protected]",
"status": "waiting_code",
"price": 1060,
"otp": [],
"codeDeadlineAt": 1753270000,
"rentExpiresAt": 1753356400,
"createdAt": 1753268200,
"warehouse": 1,
"nextCodeFee": null,
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
}
],
"nextCursor": "1753268200:abc123xyz"
}Mögliche Fehler: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITEDLager
Jeder Eintrag in
offers[] ist ein Angebot = (Dienst, Lager, Postfachtyp). Es werden nur Lager aufgelistet, die gerade verkaufen; ein Lager, das vorübergehend leer ist, bleibt mit stock = 0 in der Liste. Die Lagernummern sind fest: 1, 2, 3, 4 – übergib dieselbe Nummer im Parameter warehouse, wenn du eine Bestellung anlegst.Bestehende Clients brauchen keine Änderungen. Ohne
warehouse ⇒ dasselbe Verhalten wie bisher (Lager 1: 25 Minuten Wartezeit, zusätzliche Codes kommen automatisch innerhalb des 24-Stunden-Bestellzeitraums an, wenn der Dienst mehr als einen sendet); sende 0 ⇒ günstigstes Lager mit Bestand; sende 99 ⇒ automatisches Lager, das weitergeht, wenn eines leer ist (siehe direkt unten); jedes bestehende Feld bleibt, wie es ist – es kommen nur neue Felder hinzu (offers[], warehouse, nextCodeFee, listening, nextCodeBlocked …).Automatisches Lager: warehouse = 99 (empfohlen für Dienste, die oft ausverkauft sind)
Felder von offers[]Sende
"warehouse": 99 und das System mietet aus dem günstigsten Lager mit Bestand; ist dieses Lager leer oder fehlerhaft, geht es im selben Aufruf zum nächsten über. Ideal für Dienste, die oft ausverkauft sind, wie Facebook und Instagram – dein Bot muss offers[] nicht mehr lesen und Lager für Lager erneut versuchen.- Reihenfolge der Versuche: Preis von niedrig nach hoch (z. B. 1.500 → 1.600 → 1.700 VND); gleiche Preise ⇒ zuerst die niedrigere Lagernummer.
- Lager, die keinen Bestand melden (
stock= 0 inoffers[]), werden nie aufgerufen – keine verschwendete Zeit. - Geht weiter, wenn das aktuelle Lager leer oder fehlerhaft ist (
OUT_OF_STOCK,NO_MAILS_AVAILABLE,WAREHOUSE_UNAVAILABLE,PROVIDER_ERROR). - Andere Fehler werden sofort zurückgegeben, ohne das nächste Lager zu versuchen:
INSUFFICIENT_BALANCE,WAITING_LIMIT_REACHED,QUANTITY_EXCEEDED,MAINTENANCE,SERVICE_DISABLED,DOMAIN_UNAVAILABLE… quantity> 1: Reicht das erste Lager nicht, kommt der Rest aus dem nächsten – Bestellungen können mehrere Lager umfassen; jede Bestellung trägt daswarehouseund denpricedes Lagers, das sie ausgegeben hat (und folgt dessen Coderegeln).- Wurden alle Lager versucht und konnte kein Postfach gemietet werden ⇒
ALL_WAREHOUSES_EXHAUSTED(HTTP 409, nichts berechnet) – versuche es später erneut. Wurden nur einige Postfächer gemietet, erhältst du trotzdem 200 mit diesen Bestellungen.
curl
Python
Node.js
# warehouse: 99 – günstigstes Lager mit Bestand; leer oder fehlerhaft ⇒ automatisch das nächste Lager
curl -X POST "https://otpgmail.net/v1/orders" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"service":"fb","quantity":1,"domain":"gmail.com","warehouse":99}'¹ Dienste, die nur aus dem eigenen Bestand verkauft werden, wie Cointiply (
nextCodeFee – Gebühr für weitere Codes (Bestellungen aus Lager 2 und 3)provider = "inventory", rules.cancellable = false), folgen rules: bis zu rules.maxCodes E-Mails innerhalb von rules.windowMinutes Minuten, keine Stornierung und keine Erstattung, wenn die Zeit ohne E-Mail abläuft.Enthalten in
GET /v1/orders/{id} und in jeder Bestellantwort; null bei Bestellungen aus Lager 1 und 4 (weitere Codes sind kostenlos). Lager 2: Rufe POST /v1/orders/{id}/next-code auf, wenn du einen weiteren Code brauchst – ist feePriceVnd größer als 0, musst du confirmFee = true senden. Lager 3 (reguläre Dienste): Ist der kostenlose Zeitraum vorbei, ist feePriceVnd der Preis, um dasselbe Postfach erneut zu mieten (rerent).curl
Python
Node.js
# warehouse: 0 | 1–4 | 99 (optional – ohne Angabe bleibt es wie bisher bei Lager 1; 0 = günstigstes Lager mit Bestand; 99 = automatisch, geht weiter, wenn ein Lager leer ist; siehe offers[] in GET /v1/services)
curl -X POST "https://otpgmail.net/v1/orders" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"service":"dr","quantity":1,"domain":"gmail.com","warehouse":2}'{
"success": true,
"data": {
"orderId": "ghi789rst",
"service": "dr",
"domain": "gmail.com",
"email": "[email protected]",
"status": "completed",
"price": 350,
"otp": [
{
"code": "482913",
"receivedAt": 1753268300
}
],
"codeDeadlineAt": 1753269700,
"rentExpiresAt": 1753354200,
"createdAt": 1753267800,
"warehouse": 2,
"nextCodeFee": {
"feePriceVnd": 175,
"freeUntil": 1753289400,
"hardUntil": 1753354200,
"paidPct": 50,
"freeHours": 6
},
"listening": false,
"listenUntil": null,
"nextCodeBlocked": false,
"nextCodeBlockReason": null
}
}Sammelmiete (Sammelbestellungen)
Sammelbestellungen werden im Web gekauft (Seite Sammelmiete) – über die API kannst du Sammelbestellungen auflisten und Codes lesen. Die E-Mail-Domain bestimmt das Lager: Gmail = Lager 1, iCloud = Lager 3 (private Postfächer).
- Eine Sammelbestellung wird ab dem Kauf 24 Stunden lang aufbewahrt – lies in dieser Zeit Codes kostenlos, ohne Begrenzung der Codeanzahl.
- Postfächer, die keinen Code erhalten, werden nicht erstattet; nur eine Fehlmenge wird (automatisch) erstattet.
- Lager 1 (Gmail): Jedes Postfach hat eine
proxyUrl– einGETliefert{"status":1,"code":"482913","all_codes":[...]}ohne API-Schlüssel (der Link ist das Geheimnis – halte ihn privat). Der Code-Endpunkt unten funktioniert ebenfalls. - Lager 3 (iCloud):
proxyUrl=null– lies Codes mit dem Endpunkt unten oder öffnereadUrl(Webseite). Jedes Postfach ist für diesen Dienst an dein Konto gebunden; nach Ablauf kannst du es im Web weiterhin erneut mieten. - E-Mails ohne extrahierbaren Code (Willkommens-E-Mails, Newsletter …) kommen unverändert in
codemitisRaw: truezurück (HTML oder Text, bis zu 20.000 Zeichen).
GET/v1/batches
Deine Sammelbestellungen, neueste zuerst (seitenweise mit page / pageSize, bis zu 100). Jede Sammelbestellung hat warehouse (1 = Gmail, 3 = iCloud), status (active | expired | failed), expiresAt, codeCount und readUrl.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
page | number | Nein | Seite (Standard 1) |
pageSize | number | Nein | Sammelbestellungen pro Seite (Standard 20, max. 100) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/batches?page=1&pageSize=20" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"items": [
{
"id": "k3v9m2x7q1w8e5r4",
"serviceCode": "lf",
"serviceName": "Tiktok",
"domain": "icloud.com",
"warehouse": 3,
"status": "active",
"countRequested": 3,
"countReceived": 3,
"countDuplicate": 0,
"countCharged": 3,
"unitPriceVnd": 600,
"quotedVnd": 1800,
"chargedVnd": 1800,
"refundVnd": 0,
"holdHours": 24,
"expiresAt": 1753354200,
"createdAt": 1753267800,
"readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
"codeCount": 1,
"rerentPriceVnd": null
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}Mögliche Fehler: UNAUTHORIZEDGET/v1/batches/{id}
Eine Sammelbestellung mit ihren mails[] – nimm die id jedes Postfachs, um seine Codes zu lesen.
Gmail-Sammelbestellungen (Lager 1): Die proxyUrl jedes Postfachs funktioniert in deinen Skripten genau wie bisher. iCloud-Sammelbestellungen (Lager 3): proxyUrl = null, lies Codes mit dem Endpunkt unten. Abgelaufene iCloud-Sammelbestellungen enthalten rerentPriceVnd – den Preis, um ein Postfach einzeln erneut zu mieten (im Web).
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Sammelbestell-ID (angezeigt auf der Seite Sammelmiete und in readUrl) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/batches/BATCH_ID" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"id": "k3v9m2x7q1w8e5r4",
"serviceCode": "lf",
"serviceName": "Tiktok",
"domain": "icloud.com",
"warehouse": 3,
"status": "active",
"countRequested": 3,
"countReceived": 3,
"countDuplicate": 0,
"countCharged": 3,
"unitPriceVnd": 600,
"quotedVnd": 1800,
"chargedVnd": 1800,
"refundVnd": 0,
"holdHours": 24,
"expiresAt": 1753354200,
"createdAt": 1753267800,
"readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
"codeCount": 1,
"rerentPriceVnd": null,
"mails": [
{
"id": 812,
"email": "[email protected]",
"url": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
"proxyUrl": null,
"readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
"fresh": true,
"codeCount": 1,
"lastCode": "482913",
"lastCodeAt": 1753268500,
"lastIsRaw": false,
"checkedAt": 1753268507
}
]
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUNDGET/v1/batches/{id}/mails/{mailId}/codes
Lies die Codes eines Postfachs in einer Sammelbestellung: Das System prüft beim Anbieter, wenn das Postfach fällig ist, und liefert dann JEDEN empfangenen Code, ältester zuerst – der neueste Code steht zuletzt in codes[].
Frage während des Wartens alle 5–10 Sekunden ab (häufigeres Aufrufen liefert nur die gespeicherten Daten – polledAt ändert sich nicht). E-Mails ohne extrahierbaren Code kommen unverändert in code mit isRaw: true zurück. Abgelaufene Sammelbestellungen liefern weiterhin ihre gespeicherten Codes, werden aber nicht mehr geprüft.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Sammelbestell-ID |
mailId | number | Ja | Postfach-ID (mails[].id) |
poll | 0 | 1 | Nein | 0 = nur gespeicherte Codes lesen, den Anbieter nicht prüfen (Standard 1) |
curl
Python
Node.js
# Rufe es während des Wartens alle 5–10 Sekunden erneut auf; der neueste Code steht zuletzt in codes[]
curl "https://otpgmail.net/v1/batches/BATCH_ID/mails/MAIL_ID/codes" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"mailId": 812,
"codes": [
{
"code": "482913",
"receivedAt": 1753268500,
"isRaw": false
}
],
"polledAt": 1753268507,
"mail": {
"id": 812,
"email": "[email protected]",
"url": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
"proxyUrl": null,
"readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
"fresh": true,
"codeCount": 1,
"lastCode": "482913",
"lastCodeAt": 1753268500,
"lastIsRaw": false,
"checkedAt": 1753268507
}
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, RATE_LIMITEDBuy live Gmail accounts (2 warehouses)
Buy short-lived Gmail accounts outright: you are charged when you order and the accounts arrive within seconds. There are two mail-shop warehouses (numbered separately from the OTP-rental warehouses 1–4), each with ONE price for any quantity. Prices returned by the API are YOUR account's prices (custom price if the admin set one) — exactly what you are charged.
Warehouses- Konten aus Lager 1 (
gm_cookie) kommen ohne Wiederherstellungs-E-Mail – du bekommst E-Mail, Passwort, Cookies und den Postfach-Link (inboxUrl/inboxJsonUrl), mehr nicht. - Empfohlene Anmeldung: Cookies importieren (
cookies, JSON) – mit einer Cookie-Import-Erweiterung oder dem Cookie-Import deines Antidetect-Browsers – und dann Gmail öffnen: Die Sitzung ist bereits angemeldet, meist ohne Passwortabfrage und ohne Bestätigung. - Brauchst du nur die Codes oder E-Mails? Lies sie über
inboxUrl(Webseite) oderinboxJsonUrl(JSON) – ohne API-Schlüssel und ohne Google-Anmeldung. - Die Anmeldung mit dem Passwort auf einem neuen Gerät oder unter einer neuen IP kann dazu führen, dass Google eine Bestätigung verlangt (Wiederherstellungs-E-Mail, Telefon oder Code) – diese Konten haben nichts, womit sie sie bestehen könnten; nutze stattdessen die Cookies oder den Postfach-Link.
- Du verkaufst weiter? Gib deinen Kunden die Cookies und den Postfach-Link – der .txt-Download (
GET /v1/mail-shop/orders/{id}/download) enthältemail|password|cookies|inboxUrlin einer Zeile – und sag ihnen, dass sie sich mit den Cookies anmelden sollen, nicht mit dem Passwort. - Konten aus Lager 2 (
gm_login) kommen stattdessen mit einem Schnell-Login-Link (loginUrl) – öffne ihn zum Anmelden in einem Desktop-Browser.
- See warehouses & prices:
GET /v1/mail-shop/products⇒products[](per warehouse:priceVnd,stock,available,maxQty) +auto(warehouse 99:order,priceVnd). To see the total first, callGET /v1/mail-shop/quote. - Buy:
POST /v1/mail-shop/orderswith{"warehouse": 1 | 2 | 99, "quantity": N}+ headerIdempotency-Key(a new UUID per purchase). The response is the order, usually alreadycompletedwithitems[]. - Get the accounts: read
items[](table below) or download the TXTGET /v1/mail-shop/orders/{id}/download.status=pending⇒ pollGET /v1/mail-shop/orders/{id}every 5 seconds untilcompleted/partial/failed.
curl
Python
Node.js
# 1) warehouses, your prices, stock
curl "https://otpgmail.net/v1/mail-shop/products" -H "Authorization: Bearer og_xxx..."
# 2) buy (warehouse 1 | 2 | 99) — a NEW Idempotency-Key per purchase
curl -X POST "https://otpgmail.net/v1/mail-shop/orders" \
-H "Authorization: Bearer og_xxx..." -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" --max-time 60 \
-d '{"warehouse":99,"quantity":2}'
# 3) status "pending"? read the order again (or download the .txt)
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" -H "Authorization: Bearer og_xxx..."GET {inboxJsonUrl} (= inboxUrl + ?json) returns the same JSON as GET /api/mailbox/{code}: email, letters[] (≤ max latest emails: from, subject, receivedAt, code — extracted code / link, content — sanitized HTML), total, etag, nextAt. No API key needed (the code in the link is the key — keep it private), CORS is open. Add &max=20|50|100|200 for more emails (default 10), &etag={previous etag} ⇒ unchanged emails come back as empty letters + unchanged: true. Reading one link faster than every 10 seconds ⇒ 429 (retryAfter); link off / wrong / expired ⇒ 404; account locked ⇒ 410.curl "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json&max=20"{
"success": true,
"data": {
"email": "[email protected]",
"letters": [
{
"key": "h:9f2c4e1a7b3d5c80",
"from": "Instagram [email protected]",
"subject": "350038 is your Instagram code",
"receivedAt": 1760000520,
"code": "350038",
"content": "<html><body><p>Enter this confirmation code in the app: 350038</p></body></html>"
}
],
"max": 10,
"total": 8,
"fetchedAt": 1760000530,
"cached": false,
"etag": "f1c2a9d4e0b37a65",
"unchanged": false,
"nextAt": 1760000540,
"expiresAt": 1762592400,
"serverTime": 1760000530
}
}Read the Vinted OTP code (otpUrl)A warehouse-2 account with
otpUrl ⇒ open it in a browser to see the latest Vinted code (the page refreshes itself); GET {otpUrl}?json returns {status, code, service, email, checkedAt, retryAfter}: status = waiting (no code yet — ask again after retryAfter seconds) · ok (code = the latest code) · dead (codes can no longer be read for this account) · error (temporarily unreadable — ask again). No API key needed (the code in the link is the key — keep it private), CORS is open; the server reads the source at most every few seconds per account; more than 120 calls / minute / IP ⇒ 429.curl "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c?json"{
"success": true,
"data": {
"status": "ok",
"code": "4829",
"service": "vinted",
"email": "[email protected]",
"checkedAt": 1760000530,
"retryAfter": 5
}
}The 2FA code (twoFaUrl)Some warehouse-1 accounts have 2-step verification (2FA) on — a password sign-in makes Google ask for a 6-digit code.
GET {twoFaUrl} (or open it in a browser) returns text/plain with JUST the current 6 digits (no newline), a Refresh header = seconds until the next code + 1 (browsers reload by themselves), Cache-Control: no-store, open CORS — you can hand the link to your customers. Account without 2FA ⇒ 404 no 2fa; wrong / off / expired link ⇒ 404 not found; locked account ⇒ 410 gone; too fast / source hiccup ⇒ 429 / 502 retry (Retry-After header). With an API key: GET /v1/mail-shop/orders/{id}/items/{lineNo}/2fa ⇒ {none, code, expiresIn}. The server caches the code until it expires. The .txt file (GET /v1/mail-shop/orders/{id}/download): add ?twofa=1 ⇒ a last twoFaUrl column (off by default — the old layout is unchanged).curl "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d"
# 424819Order statuses- Money:
chargedVnd= Σroute[].quantity×unitPriceVnd(a 99 order split across warehouses ⇒ two legs inroute). Shortfalls are refunded automatically (refundVnd). Delivered accounts are not refunded — the only warranty is the warehouse'sterms(read them first). - Idempotency-Key is required (400 without it): retrying with the SAME key after a timeout returns the same order and charges nothing more; a new key buys again. Use a client timeout of at least 60 seconds.
- Limits: at most 500 accounts per order (and the warehouse's
maxQty), 10 purchases per minute for your account (web + API share it), 180 inbox reads per hour. Above ⇒ 429RATE_LIMITED. - Common errors:
MAILSHOP_OUT_OF_STOCK409 (details.stock= how many you can buy now) ·MAILSHOP_QTY_LIMIT400 (details.max) ·INSUFFICIENT_BALANCE402 ·MAILSHOP_DISABLED503 (warehouse / section paused) ·MAILSHOP_SUPPLIER_ERROR502 (the source refused at once — a failed order was recorded and refunded,details.orderId) ·VALIDATION_ERROR400 (warehouse not 1 | 2 | 99, warehouse and product disagree, missing Idempotency-Key…). - Terms (warranty only for the first sign-in within 2 hours, accounts last ~6–24 hours) are in each warehouse's
terms; the API does not ask you to accept them (the web asks once). API orders show up in your web history (channel=api) and vice versa.
GET/v1/mail-shop/products
The two mail-shop warehouses + automatic warehouse 99. products[]: warehouse (1 | 2), code (gm_cookie | gm_login), delivers (fields of every account), priceVnd (YOUR price), listPriceVnd (list price), stock, available, maxQty (most one order can take now). auto: order (the order warehouse 99 buys in), priceVnd (cheapest warehouse), available.
Warehouse 1 stock = what the source publishes (refreshed every minute; the source checks accounts are live at purchase time, so you may get fewer — the shortfall is refunded). Warehouse 2 = a reference number from a source sharing the same stock, may be off by a few (null = could not be read — just buy); a shortfall becomes a partial order with the rest refunded. available = false ⇒ do not buy from that warehouse (out of stock / paused). Prices can change — read the catalog again before a large purchase.
Beispielcurl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/products" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"products": [
{
"warehouse": 1,
"code": "gm_cookie",
"name": "Short-lived live Gmail — with cookies + inbox link",
"description": "Short-lived personal Gmail account. You get the email · password · login cookies · an inbox link (add ?json to read the emails as JSON).",
"terms": "• Warranty covers ONLY the first successful sign-in, within 2 hours of purchase.\n• These Gmail accounts typically last 6–24 hours: use them right after purchase.",
"delivers": [
"email",
"password",
"cookies",
"inboxUrl",
"inboxJsonUrl"
],
"priceVnd": 3300,
"listPriceVnd": 3300,
"stock": 1840,
"available": true,
"maxQty": 500
},
{
"warehouse": 2,
"code": "gm_login",
"name": "Short-lived live Gmail — with a quick login link",
"description": "Short-lived personal Gmail account that has been used on a few services. You get the email · password · a quick login link.",
"terms": "• Warranty covers ONLY the first successful sign-in, within 2 hours of purchase.\n• Sign in with the link in a DESKTOP BROWSER.",
"delivers": [
"email",
"password",
"loginUrl"
],
"priceVnd": 6000,
"listPriceVnd": 6000,
"stock": 141,
"available": true,
"maxQty": 100
}
],
"auto": {
"warehouse": 99,
"priceVnd": 3300,
"available": true,
"order": [
1,
2
]
}
}
}Mögliche Fehler: UNAUTHORIZEDGET/v1/mail-shop/quote
Call quote to see the total first (nothing is reserved). Warehouse 99 ⇒ route[] = planned split by known stock; available = false ⇒ that quantity cannot be bought right now.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
warehouse | 1 | 2 | 99 | Nein | Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product |
product | string | Nein | Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2) |
quantity | number | Nein | Number of accounts (default 1) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/quote?warehouse=99&quantity=10" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"warehouse": 99,
"quantity": 10,
"unitPriceVnd": 3300,
"totalVnd": 38400,
"available": true,
"stock": null,
"maxQty": 500,
"route": [
{
"warehouse": 1,
"quantity": 8,
"unitPriceVnd": 3300
},
{
"warehouse": 2,
"quantity": 2,
"unitPriceVnd": 6000
}
]
}
}Mögliche Fehler: UNAUTHORIZED, VALIDATION_ERROR, MAILSHOP_PRODUCT_NOT_FOUNDPOST/v1/mail-shop/orders
Buy: you are charged at once, then the system buys from the source and delivers. The response is the order — usually already completed with items[]; status = pending ⇒ poll GET /v1/mail-shop/orders/{id} every 5 seconds. The call waits up to about 16 seconds for the delivery.
Idempotency-Key is required (400 without it). Not enough stock ⇒ MAILSHOP_OUT_OF_STOCK (error.details.stock = how many you can buy now, nothing charged); over the per-order limit ⇒ MAILSHOP_QTY_LIMIT (error.details.max). 502 MAILSHOP_SUPPLIER_ERROR = the source refused at once: a failed order was recorded and fully refunded (error.details.orderId) — retry with a new key.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
warehouse | 1 | 2 | 99 | Nein | Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product |
product | string | Nein | Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2) |
quantity | number | Nein | Number of accounts (default 1) |
Idempotency-Key | string | Ja | Header: a new UUID for every new purchase — reuse it only to retry that same purchase |
curl
Python
Node.js
# Idempotency-Key ist erforderlich: eine neue UUID pro Kauf – verwende denselben Schlüssel nur für einen erneuten Versuch nach einem Timeout
# warehouse: 1 = cookies + inbox link · 2 = login link · 99 = cheapest first
curl -X POST "https://otpgmail.net/v1/mail-shop/orders" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--max-time 60 \
-d '{"warehouse":99,"quantity":5}'{
"success": true,
"data": {
"id": "p3x9k2m7q1w8e5r4",
"requestedWarehouse": 99,
"warehouse": 1,
"productCode": "gm_cookie",
"quantity": 2,
"deliveredCount": 2,
"unitPriceVnd": 3300,
"chargedVnd": 9300,
"refundVnd": 0,
"status": "completed",
"channel": "api",
"route": [
{
"warehouse": 1,
"quantity": 1,
"unitPriceVnd": 3300
},
{
"warehouse": 2,
"quantity": 1,
"unitPriceVnd": 6000
}
],
"message": null,
"createdAt": 1760000400,
"completedAt": 1760000409,
"items": [
{
"lineNo": 1,
"warehouse": 1,
"email": "[email protected]",
"password": "Pw7Xq!k3d",
"rawLine": "[email protected]|Pw7Xq!k3d||[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
"cookies": "[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
"inboxUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d",
"inboxJsonUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json",
"twoFaUrl": "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d",
"inboxLinkExpiresAt": 1762592400
},
{
"lineNo": 2,
"warehouse": 2,
"email": "[email protected]",
"password": "Qp9!rT2",
"rawLine": "[email protected]!rT2----no----https://login.example/google/login?uid=s2c38a3b",
"loginUrl": "https://login.example/google/login?uid=s2c38a3b",
"otpUrl": "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c",
"otpService": "vinted"
}
]
}
}Mögliche Fehler: UNAUTHORIZED, VALIDATION_ERROR, MAILSHOP_PRODUCT_NOT_FOUND, MAILSHOP_DISABLED, MAILSHOP_QTY_LIMIT, MAILSHOP_OUT_OF_STOCK, INSUFFICIENT_BALANCE, MAILSHOP_SUPPLIER_ERROR, RATE_LIMITED, DUPLICATE_REQUEST, MAINTENANCEGET/v1/mail-shop/orders
Your mail-shop orders, newest first (page / pageSize, up to 100 per page) — without items[]: fetch an order to get its accounts. Orders bought on the web are listed too (channel = web).
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
page | number | Nein | Page (default 1) |
pageSize | number | Nein | Orders per page (default 20, max 100) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/orders?page=1&pageSize=20" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"items": [
{
"id": "p3x9k2m7q1w8e5r4",
"requestedWarehouse": 99,
"warehouse": 1,
"productCode": "gm_cookie",
"quantity": 2,
"deliveredCount": 2,
"unitPriceVnd": 3300,
"chargedVnd": 9300,
"refundVnd": 0,
"status": "completed",
"channel": "api",
"route": [
{
"warehouse": 1,
"quantity": 1,
"unitPriceVnd": 3300
},
{
"warehouse": 2,
"quantity": 1,
"unitPriceVnd": 6000
}
],
"message": null,
"createdAt": 1760000400,
"completedAt": 1760000409
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}Mögliche Fehler: UNAUTHORIZEDGET/v1/mail-shop/orders/{id}
One order with every account delivered so far (an order still delivering already shows what has arrived). route[] = which warehouse, how many, at what price.
Poll every 5 seconds while status = pending; completed / partial / failed / refunded are final. An order of many warehouse-1 accounts (cookies) can weigh several MB — stop polling once it is final.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Order ID (data.id returned by the purchase) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"id": "p3x9k2m7q1w8e5r4",
"requestedWarehouse": 99,
"warehouse": 1,
"productCode": "gm_cookie",
"quantity": 2,
"deliveredCount": 2,
"unitPriceVnd": 3300,
"chargedVnd": 9300,
"refundVnd": 0,
"status": "completed",
"channel": "api",
"route": [
{
"warehouse": 1,
"quantity": 1,
"unitPriceVnd": 3300
},
{
"warehouse": 2,
"quantity": 1,
"unitPriceVnd": 6000
}
],
"message": null,
"createdAt": 1760000400,
"completedAt": 1760000409,
"items": [
{
"lineNo": 1,
"warehouse": 1,
"email": "[email protected]",
"password": "Pw7Xq!k3d",
"rawLine": "[email protected]|Pw7Xq!k3d||[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
"cookies": "[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
"inboxUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d",
"inboxJsonUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json",
"twoFaUrl": "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d",
"inboxLinkExpiresAt": 1762592400
},
{
"lineNo": 2,
"warehouse": 2,
"email": "[email protected]",
"password": "Qp9!rT2",
"rawLine": "[email protected]!rT2----no----https://login.example/google/login?uid=s2c38a3b",
"loginUrl": "https://login.example/google/login?uid=s2c38a3b",
"otpUrl": "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c",
"otpService": "vinted"
}
]
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUNDGET/v1/mail-shop/orders/{id}/download
The delivered accounts as .txt (text/plain), separated by |. The FIRST LINE is the column header: warehouse 1 email|password|cookies|inboxUrl · warehouse 2 email|password|loginUrl · a 99 order with both warehouses ⇒ email|password|cookies|inboxUrl|loginUrl (missing cells empty).
cookies is one-line JSON and may contain | — split email and password at the first two |, the link at the last | (with extra columns ⇒ the links after cookies at the last | separators — links never contain |; read the header line for their order). Nothing delivered yet ⇒ 409 VALIDATION_ERROR. Add <c>?otp=1</c> ⇒ warehouse 2 gets a LAST column <c>otpUrl</c> (the Vinted OTP link — empty when the account has none): email|password|loginUrl|otpUrl. Without it ⇒ exactly the columns above. Add <c>?twofa=1</c> ⇒ warehouse 1 gets a LAST column <c>twoFaUrl</c> (the 2FA link — same code as inboxUrl; GET ⇒ just the 6 digits; empty when the link is off / expired): email|password|cookies|inboxUrl|twoFaUrl. Combines with <c>otp=1</c> — fixed column order: …|loginUrl|otpUrl|twoFaUrl (a 99 order with both warehouses). Without it ⇒ the default layout above.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Order ID (data.id returned by the purchase) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/download" \
-H "Authorization: Bearer og_xxx..." \
-o mail-ORDER_ID.txtemail|password|cookies|inboxUrl
[email protected]|Pw7Xq!k3d|[{"domain":".google.com","name":"SID","value":"g.a000…"}]|https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1dMögliche Fehler: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERRORGET/v1/mail-shop/orders/{id}/items/{lineNo}/inbox
Latest emails of a WAREHOUSE-1 account (sender, subject, time, extracted code / link, sanitized full email). Without an API key: GET the account's inboxJsonUrl (same emails, JSON).
max = 10 (default) | 20 | 50 | 100 | 200 — more emails take longer (up to about a minute). Cached for 15 seconds; every real read counts toward the hourly limit. 409 VALIDATION_ERROR = a warehouse-2 account (no inbox reading); 409 PROVIDER_ERROR with details.dead = the account was locked or removed.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Order ID (data.id returned by the purchase) |
lineNo | number | Ja | Account number within the order (items[].lineNo) |
max | number | Nein | Number of latest emails: 10 (default), 20, 50, 100 or 200 |
curl
Python
Node.js
# Warehouse 1 accounts only; max = 10 | 20 | 50 | 100 | 200
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/inbox?max=20" \
-H "Authorization: Bearer og_xxx..." \
--max-time 70
# Same emails without an API key: GET the account's inboxJsonUrl
curl "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json&max=20"{
"success": true,
"data": {
"email": "[email protected]",
"letters": [
{
"key": "h:9f2c4e1a7b3d5c80",
"from": "Instagram [email protected]",
"subject": "350038 is your Instagram code",
"receivedAt": 1760000520,
"code": "350038",
"content": "<html><body><p>Enter this confirmation code in the app: 350038</p></body></html>"
}
],
"max": 10,
"total": 8,
"fetchedAt": 1760000530,
"cached": false
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERRORPOST/v1/mail-shop/orders/{id}/items/{lineNo}/inbox-link
Manage the inbox link (inboxUrl + inboxJsonUrl + twoFaUrl — one shared code) of a warehouse-1 account: action "regenerate" = NEW link (the old one stops working immediately; also re-enables a link you turned off), "off" = turn it off (inboxUrl / inboxJsonUrl / twoFaUrl become null). Returns the account (an element of items[]).
At most 60 changes per 10 minutes for your account.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Order ID (data.id returned by the purchase) |
lineNo | number | Ja | Account number within the order (items[].lineNo) |
action | string | Ja | "regenerate" = new link (the old one stops working immediately) · "off" = turn the link off |
curl
Python
Node.js
# Neuer Link (der alte funktioniert sofort nicht mehr); sende {"action":"off"}, um ihn auszuschalten
curl -X POST "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/inbox-link" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-d '{"action":"regenerate"}'{
"success": true,
"data": {
"lineNo": 1,
"warehouse": 1,
"email": "[email protected]",
"password": "Pw7Xq!k3d",
"rawLine": "[email protected]|Pw7Xq!k3d||[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
"cookies": "[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
"inboxUrl": "https://otpgmail.net/mail/Hn4Vw8Zr2Lk6Tq9Pm3Xs7Bd1",
"inboxJsonUrl": "https://otpgmail.net/mail/Hn4Vw8Zr2Lk6Tq9Pm3Xs7Bd1?json",
"twoFaUrl": "https://otpgmail.net/2fa/Hn4Vw8Zr2Lk6Tq9Pm3Xs7Bd1",
"inboxLinkExpiresAt": 1762592400
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITEDGET/v1/mail-shop/orders/{id}/items/{lineNo}/status
Is a bought warehouse-1 account still alive: live = true / false, or null when it cannot be determined. Counts toward the hourly inbox-read limit.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Order ID (data.id returned by the purchase) |
lineNo | number | Ja | Account number within the order (items[].lineNo) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/status" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"email": "[email protected]",
"live": true,
"reason": null,
"checkedAt": 1760000600
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERRORGET/v1/mail-shop/orders/{id}/items/{lineNo}/2fa
The current 2FA code (6-digit TOTP, changes every 30 seconds) of a warehouse-1 account — some warehouse-1 accounts have 2-step verification on (Google asks for a code at a password sign-in). none = true ⇒ the account has no 2FA (code / expiresIn null). expiresIn = seconds the code stays valid — ask again after that. Without an API key: GET the account's twoFaUrl (the body is just the 6 digits).
The server caches the code until it expires (every call within those 30 seconds gets the same code) — it does NOT count toward the hourly inbox-read limit. Calling too fast ⇒ 429 (retryAfterSec); a warehouse-2 account ⇒ 409; a locked account ⇒ 409 (details.dead).
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Order ID (data.id returned by the purchase) |
lineNo | number | Ja | Account number within the order (items[].lineNo) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/2fa" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"none": false,
"code": "424819",
"expiresIn": 24
}
}Mögliche Fehler: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERRORLebenszyklus einer Bestellung
Wichtige Hinweise:
- Frage
GET /v1/orders/{id}alle 3–5 Sekunden ab, solange die Bestellung waiting_code ist, damit du den Code zügig abholst. - Sobald der erste Code ankommt, wechselt der Status sofort auf completed.
- Kein Code bis
codeDeadlineAt(höchstens 25 Minuten; Lager 2: 20 Minuten; einige Quellen von Lager 4 sind kürzer) ⇒ das System storniert die Bestellung und erstattet 100 % (status = cancelled). - Das Array
otp[]ist ältester zuerst, neuester zuletzt sortiert. Lies den neuesten Code mitotp[otp.length - 1](Python:otp[-1]). - Eine completed-Bestellung empfängt weiter automatisch Codes: Frage
GET /v1/orders/{id}weiter ab, und jeder neue Code erscheint am Ende vonotp[]. Zusätzliche Codes kosten innerhalb des Bestellzeitraums nichts, wenn der Dienst mehr als einen sendet; das Limit und das Zeitfenster werden vor dem Mieten angezeigt (maxCodesundfeeinoffers[]; bei Lager 1 bleibt das Postfach nach dem letzten Code meist etwa 25 Minuten offen). Um ausdrücklich einen weiteren Code anzufordern (z. B. um dich nach der Registrierung anzumelden):POST /v1/orders/{id}/next-code– funktioniert in jedem Lager, und nur Lager 2 berechnet außerhalb seines kostenlosen Zeitraums eine Gebühr. - Erlaubt der Dienst nur einen Code pro Postfach, endet
otp[]bei diesem Code. Die Antworten bleiben normal, ohne Fehler – du entscheidest, wann du mit dem Abfragen aufhörst. otp[].codeist meist ein kurzer Code (Ziffern / Buchstaben) oder ein Link. Eine E-Mail ohne extrahierbaren Code (Willkommens-E-Mails, Newsletter, ungewöhnliche Codeformate …) wird unverändert (HTML oder Text) inotp[].codegeliefert – behandle sie als vollständige E-Mail, wenn sie länger als 80 Zeichen ist, einen Zeilenumbruch enthält oder HTML ist, und suche den Code oder Link selbst darin.POST /v1/orderswartet, bis der Anbieter ein Postfach zuweist, dauert also meist ~0,3 Sekunden, kann zu Spitzenzeiten (selten) aber bis zu 10 Sekunden dauern. Stelle ein Client-Timeout von ≈ 15 Sekunden ein, statt nach 5 Sekunden aufzugeben und zu früh erneut zu versuchen – und verwende bei einem erneuten Versuch denselbenIdempotency-Key.- Eine Bestellung, die einen Code erhalten hat, kann nicht storniert oder erstattet werden – der Stornoaufruf liefert
ORDER_NOT_CANCELLABLE. - Um ein @icloud.com-Postfach zu mieten, sende beim Anlegen der Bestellung
"domain": "icloud.com"(für Gmail weglassen; bestehende Bots brauchen keine Änderungen). Unterstützt der Dienst iCloud nicht oder ist iCloud vorübergehend deaktiviert, erhältst duDOMAIN_UNAVAILABLE– wechsle zu Gmail, und die Miete geht sofort durch. Der Lebenszyklus, die Regeln für Stornierung und Erstattung und die Art, Codes zu lesen, sind genau dieselben wie bei Gmail. - Dienste aus eigenem Bestand (
provider="inventory", z. B. Cointiply – sieheGET /v1/services) gibt es nur mit iCloud; jede E-Mail kommt als Link inotp[].codean, zusammen mitkind,labelund dem vollständigen E-Mail-Text intext. Eine Bestellung empfängt bis zurules.maxCodesE-Mails innerhalb vonrules.windowMinutesMinuten (biswindowEndsAt), kann nicht storniert werden (ORDER_NOT_CANCELLABLE), und läuft die Zeit ohne E-Mail ab, wird sie ohne Erstattung zu cancelled. Um auf demselben Postfach weiter zu empfangen, rufePOST /v1/orders/{id}/rerentauf, solangecanRerenttrue ist (Preis:rerentPriceVnd). Alle anderen Dienste / Bestellungen habenprovider="standard"— seit dem 2026-10-24 hatprovidernur zwei Werte,"standard"|"inventory"(das tatsächliche Lager steht inwarehouse); vergleiche nur mit"inventory". - Bestellungen aus Lager 2 (
warehouse= 2): Codes sindfee.freeHoursStunden lang kostenlos; schließt das kostenlose Fenster, kommen keine neuen Codes mehr an – rufePOST /v1/orders/{id}/next-codemitconfirmFee= true auf (und zahlenextCodeFee.feePriceVnd), um ein weiteres Fenster zu öffnen, bis zunextCodeFee.hardUntil. Bestellungen aus Lager 3 bei regulären Diensten: 24 Stunden kostenlose Codes, vor dem Eintreffen eines Codes stornierbar, danach nutzererent. Bestellungen aus Lager 4: neue Codes landen beim Lesen der Bestellung inotp[], kostenlos bisrentExpiresAt; bei einer Quelle mit nur einem Code wirdnextCodeBlockReasonnach dem ersten Code zu"max_codes". - Viele Bestellungen gleichzeitig? Frage bis zu 100 pro Aufruf mit
POST /v1/orders/statusab – siehe Status vieler Bestellungen in einem Aufruf.
Rate-Limits
Die Limits gelten pro API-Schlüssel (ein Konto kann eigene Limits haben; sonst gilt der Standard der Website):
- 100 Anfragen/Sekunde – kurze Spitzen über wenige Sekunden
- 3000 Anfragen/Minute – Gesamtverkehr pro Minute
Wird ein Limit überschritten, liefert die API HTTP 429 mit einem Header
Retry-After: <seconds> und dem Body {"success":false,"error":{"code":"RATE_LIMITED",...}}. Warte vor dem nächsten Versuch die im Header angegebene Anzahl an Sekunden. Der Fehler-Body enthält dieselbe Sekundenzahl in error.retryAfterSec (mit error.retryable = true).Fehlercodes
Jeder Fehler hat dieselbe Form:
{"success":false,"error":{"code":"...","message":"...","retryable":false,"retryAfterSec":null}}Seit 2026-10-17 hat jeder /v1-Fehler zusätzlich
Beispiel-Fehlerantwortenerror.retryable und error.retryAfterSec (neue Felder – bestehende Clients müssen nichts ändern; details bleibt unverändert). retryable = true ⇒ derselbe Aufruf kann später klappen: warte retryAfterSec Sekunden (ganze Zahl ≥ 1) und sende ihn erneut – ausverkauft / keine Postfächer / Lager pausiert / Anbieterfehler ⇒ 5 s; 429 RATE_LIMITED ⇒ der Header Retry-After; 409 DUPLICATE_REQUEST, solange die erste Anfrage mit demselben Idempotency-Key noch läuft ⇒ 2 s. retryable = false (retryAfterSec = null) ⇒ erneutes Aufrufen hilft nicht, bis du die Ursache behebst (Guthaben, Limit wartender Bestellungen, Parameter, API-Schlüssel …). partial.reason von POST /v1/orders folgt denselben Regeln. Bei einer 409-Antwort „nicht vorrätig“ (OUT_OF_STOCK, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED) die vollen retryAfterSec Sekunden warten, bevor Sie es erneut versuchen — ein früherer Versuch liefert meist nur dieselbe Antwort — und vor dem nächsten Mieten stock / offers[].stock in GET /v1/services prüfen; bereits gemietete Bestellungen per Webhook verfolgen, statt sie ständig abzufragen.HTTP/1.1 409 Conflict
{
"success": false,
"error": {
"code": "OUT_OF_STOCK",
"message": "This service is currently out of stock",
"retryable": true,
"retryAfterSec": 5
}
}
HTTP/1.1 429 Too Many Requests
Retry-After: 1
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded, retry after 1s",
"retryable": true,
"retryAfterSec": 1
}
}
HTTP/1.1 402 Payment Required
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient balance to rent an inbox",
"retryable": false,
"retryAfterSec": null
}
}Webhooks (Push-Benachrichtigungen)
Registrieren Sie eine URL pro Konto, und wir senden per POST ein signiertes JSON-Ereignis dorthin, sobald eine Bestellung einen neuen Code erhält (
Einrichtung in 3 Schrittenorder.code) oder storniert / erstattet wird (order.cancelled). Webhooks sind optional und gelten für alle Bestellungen Ihres Kontos (Web und API). Die Zustellung erfolgt nach bestem Bemühen — fragen Sie als Rückfallebene weiterhin in großen Abständen ab (siehe unten).PUT /v1/webhookmit Ihrer URL (optional mitevents) — speichern Siedata.secret(64 Hex-Zeichen): Sie brauchen es, um jede Zustellung zu prüfen. Ein erneuter PUT-Aufruf behält dasselbe Secret.POST /v1/webhook/test— wir senden sofort einping-Ereignis und liefern zurück, was Ihr Server geantwortet hat (ok,httpStatus,error).- Ihr Endpunkt muss innerhalb von 5 Sekunden mit einem beliebigen 2xx antworten — zuerst antworten, dann das Ereignis verarbeiten. Alles andere (3xx, 4xx, 5xx, Zeitüberschreitung, Verbindungsfehler) gilt als Fehlschlag und wird wiederholt.
Die URL muss mit
Format der Anfragehttp:// oder https:// beginnen (gültiges Zertifikat) und aus dem Internet erreichbar sein: localhost, private, Link-Local-, Multicast- und reservierte Adressen (10.x, 172.16–31.x, 192.168.x, 169.254.x, fc00::/7, fe80::/10…) sowie die eigenen Server von OTPGmail werden mit VALIDATION_ERROR abgelehnt. Der Hostname wird vor jeder Zustellung neu aufgelöst. Weiterleitungen werden nicht verfolgt — registrieren Sie die endgültige URL.Jede Zustellung ist ein HTTP-POST an Ihre URL mit JSON-Body (UTF-8) und diesen Headern:
Berechnen Sie HMAC-SHA256 mit Ihrem Secret als Schlüssel über
Empfänger-Beispiele (Python und Node nutzen nur die Standardbibliothek; der curl-Befehl sendet eine korrekt signierte Testzustellung an Ihren eigenen Empfänger)X-OTPGmail-Timestamp + . + den rohen Request-Body als Hex in Kleinbuchstaben und vergleichen Sie das Ergebnis in konstanter Zeit mit dem Wert nach sha256=. Der Schlüssel ist die 64-stellige Secret-Zeichenkette selbst (ihre UTF-8-Bytes — nicht hex-dekodieren). Verwenden Sie die rohen Bytes: Parsen und erneutes Serialisieren des JSON verändert sie. Lehnen Sie Zeitstempel ab, die mehr als 5 Minuten von Ihrer Uhr abweichen (Schutz vor Replays).curl
Python
Node.js
# Lokaler Test: Senden Sie Ihrem Empfänger eine Zustellung, die genau wie unsere signiert ist
SECRET="9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e"
TS=$(date +%s)
BODY='{"event":"ping","deliveryId":"6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8","sentAt":'$TS'}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -X POST "http://localhost:8080/" \
-H "Content-Type: application/json" \
-H "X-OTPGmail-Event: ping" \
-H "X-OTPGmail-Delivery: 6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8" \
-H "X-OTPGmail-Timestamp: $TS" \
-H "X-OTPGmail-Signature: sha256=$SIG" \
-d "$BODY"{
"event": "order.code",
"deliveryId": "6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8",
"sentAt": 1760700125,
"orderId": "k3m9x2p7q1w8e5r4",
"service": "fb",
"warehouse": 1,
"domain": "gmail.com",
"email": "[email protected]",
"code": "482913",
"receivedAt": 1760700124,
"status": "completed",
"otp": [
{
"code": "482913",
"receivedAt": 1760700124
}
]
}Beispiel-Body — order.cancelled{
"event": "order.cancelled",
"deliveryId": "0b7e4c2a-5d1f-4e8b-9a3c-2f6d8e1b4a7c",
"sentAt": 1760701630,
"orderId": "p8v2n5c1x7z3m9k4",
"service": "ig",
"warehouse": 2,
"domain": "gmail.com",
"email": "[email protected]",
"reason": "timeout",
"refundVnd": 1500,
"status": "cancelled"
}Beispiel-Body — ping{
"event": "ping",
"deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f",
"sentAt": 1760700000
}Wiederholungen, Pausieren und Verlauf- Eine fehlgeschlagene Zustellung wird nach 10 s, 1 Min., 5 Min., 30 Min. und 2 Std. wiederholt (5 Wiederholungen); schlägt auch die letzte fehl, wird das Ereignis verworfen (
failed). - 50 Fehlversuche in Folge (über alle Ereignisse) pausieren den Webhook:
GET /v1/webhookzeigtpausedAt, es wird nichts gesendet und neue Ereignisse werden nicht eingereiht. - Zum Fortsetzen:
PUT /v1/webhookerneut aufrufen (dieselbe URL genügt) oder einen erfolgreichenPOST /v1/webhook/testausführen — danach werden die wartenden Ereignisse gesendet. GET /v1/webhook/deliverieslistet die letzten 100 Zustellungen mit Status, Versuchen und letztem Fehler; der Verlauf wird 7 Tage aufbewahrt.
Dasselbe Ereignis kann mehrfach ankommen (etwa wenn Ihre 2xx-Antwort verloren ging), und Ereignisse können außer der Reihe ankommen (mehrere werden parallel gesendet). Speichern Sie verarbeitete
Abfragen als Rückfallebene beibehaltendeliveryIds und überspringen Sie Wiederholungen; verlassen Sie sich bei einer Bestellung auf ihre neuesten Daten (otp, status) statt auf die Ankunftsreihenfolge.Webhooks können sich verzögern (Wiederholungen) oder ausbleiben (Webhook pausiert, Ihr Server nicht erreichbar). Fragen Sie weiterhin in großen Abständen ab — etwa
GET /v1/orders/{id} alle 30–60 s für Bestellungen, die noch waiting_code sind — und betrachten Sie das Bestellobjekt der API als maßgeblich.PUT/v1/webhook
Registriert oder aktualisiert den Webhook Ihres Kontos. Der erste Aufruf legt ihn an und liefert ein neues Secret; spätere Aufrufe behalten das Secret, schalten den Webhook wieder ein und setzen ihn fort, falls er pausiert war. Ohne events: beide Ereignisse für einen neuen Webhook, unverändert für einen bestehenden. Die URL muss eine öffentliche Adresse sein — sonst 400 VALIDATION_ERROR (den Grund nennt error.details.problem).
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
url | string | Ja | Ihr Endpunkt (http:// oder https://, öffentliche Adresse, höchstens 2048 Zeichen) |
events | string[] | Nein | Welche Ereignisse gesendet werden: ["order.code", "order.cancelled"] (Standard: beide) |
curl
Python
Node.js
# data.secret speichern — Sie brauchen es, um jede Zustellung zu prüfen
curl -X PUT "https://otpgmail.net/v1/webhook" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-d '{"url":"https://your-server.example/otpgmail-webhook","events":["order.code","order.cancelled"]}'{
"success": true,
"data": {
"url": "https://your-server.example/otpgmail-webhook",
"events": [
"order.code",
"order.cancelled"
],
"enabled": true,
"secret": "9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e",
"pausedAt": null,
"consecutiveFailures": 0,
"createdAt": 1760699000,
"updatedAt": 1760699000
}
}Mögliche Fehler: UNAUTHORIZED, VALIDATION_ERRORGET/v1/webhook
Der Webhook Ihres Kontos, einschließlich secret, pausedAt und consecutiveFailures. data ist null, wenn kein Webhook registriert ist.
Beispielcurl
Python
Node.js
curl "https://otpgmail.net/v1/webhook" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"url": "https://your-server.example/otpgmail-webhook",
"events": [
"order.code",
"order.cancelled"
],
"enabled": true,
"secret": "9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e",
"pausedAt": 1760705400,
"consecutiveFailures": 50,
"createdAt": 1760699000,
"updatedAt": 1760705400
}
}Mögliche Fehler: UNAUTHORIZEDDELETE/v1/webhook
Entfernt den Webhook; noch wartende Ereignisse werden verworfen. Immer 200 — data.deleted ist false, wenn es nichts zu entfernen gab.
Beispielcurl
Python
Node.js
curl -X DELETE "https://otpgmail.net/v1/webhook" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"deleted": true
}
}Mögliche Fehler: UNAUTHORIZEDPOST/v1/webhook/test
Sendet sofort ein ping-Ereignis an Ihre URL und meldet das Ergebnis: ok (Ihr Server hat mit 2xx geantwortet), httpStatus, durationMs, error. Ein erfolgreicher Test setzt einen pausierten Webhook fort. Höchstens 10 Tests pro Minute; 503 MAINTENANCE, solange Webhooks systemweit abgeschaltet sind.
Beispielcurl
Python
Node.js
curl -X POST "https://otpgmail.net/v1/webhook/test" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"ok": true,
"httpStatus": 200,
"durationMs": 143,
"error": null,
"deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f"
}
}Antwort 200, wenn Ihr Server nicht mit 2xx antwortet
Mögliche Fehler: {
"success": true,
"data": {
"ok": false,
"httpStatus": 500,
"durationMs": 88,
"error": "HTTP 500",
"deliveryId": "7d2b9f4e-1c6a-4e3b-8d5f-0a9c2e7b1f4d"
}
}UNAUTHORIZED, NOT_FOUND, RATE_LIMITED, MAINTENANCEGET/v1/webhook/deliveries
Letzte Zustellungen, die neuesten zuerst (7 Tage aufbewahrt): status = queued (nächster Versuch zu nextAt) | delivered | failed | paused, mit attempts, lastHttpStatus und lastError. Die Bodys der Ereignisse werden nicht zurückgegeben.
Parameter| Name | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
limit | number | Nein | Anzahl der Zustellungen, 1–100 (Standard 100) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/webhook/deliveries?limit=20" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": [
{
"id": "0b7e4c2a-5d1f-4e8b-9a3c-2f6d8e1b4a7c",
"event": "order.cancelled",
"orderId": "p8v2n5c1x7z3m9k4",
"status": "queued",
"attempts": 1,
"lastHttpStatus": 503,
"lastError": "HTTP 503",
"nextAt": 1760701640,
"createdAt": 1760701630,
"deliveredAt": null
},
{
"id": "6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8",
"event": "order.code",
"orderId": "k3m9x2p7q1w8e5r4",
"status": "delivered",
"attempts": 1,
"lastHttpStatus": 200,
"lastError": null,
"nextAt": null,
"createdAt": 1760700124,
"deliveredAt": 1760700125
}
]
}Mögliche Fehler: UNAUTHORIZED