Terapkan diskon khususBagi siapa saja yang menyewa OTP Gmail > 2000 Gmail per hari untuk layanan apa pun (kecuali Facebook, Instagram, dan Apple) dan ingin diskon khusus, silakan hubungi saya ya! Diskon bisa mencapai hingga 50% dari harga asli bagi yang mendaftar akun dalam jumlah yang cukup besar.
New update 29/09I have just added 4 new warehouses for all services + short-live accounts shop (24h) with email + pass for you to log in, feel free to check them out when you have time

Dokumentasi API

API publik /v1 — autentikasi dengan API key dan otomatiskan penyewaan kotak masuk dari perangkat lunak Anda sendiri

API key Anda
Anda belum masuk — masuk untuk mendapatkan API key Anda sendiri.
Kirim key ini di header Authorization: Bearer <key> atau parameter query ?api_key=<key>. Klik ikon mata untuk menampilkan key lengkap; klik Buat key baru bila Anda perlu menggantinya (key lama langsung berhenti berfungsi).

Ringkasan

Base URL
https://otpgmail.net
Autentikasi
Sertakan API key Anda di setiap permintaan, baik di header Authorization: Bearer og_xxx... (disarankan) maupun di query string ?api_key=og_xxx....
Idempotency-Key (mencegah pesanan ganda)
Setiap permintaan POST /v1/orders sebaiknya menyertakan header Idempotency-Key: <UUID baru per pesanan>. Jika permintaan timeout atau koneksi terputus, kirim ulang dengan key yang sama — server mengembalikan respons aslinya dan tidak membuat pesanan lagi. Buat UUID baru untuk setiap pesanan baru dan pakai ulang UUID hanya saat mengulang permintaan.
Banyak pesanan sekaligus: status dalam satu panggilan
Menyewa puluhan atau ratusan kotak masuk? Cek statusnya sekaligus: POST /v1/orders/status mengembalikan hingga 100 pesanan per panggilan (dihitung satu permintaan pada rate limit Anda), dan GET /v1/orders menampilkan daftar pesanan Anda — setelah restart, ?status=waiting_code mengembalikan semua pesanan yang masih menunggu kode. Setiap error juga memberi tahu apakah perlu dicoba lagi (error.retryable, error.retryAfterSec). Detail: bagian Status banyak pesanan.
Jenis kotak masuk: Gmail atau iCloud
Secara default Anda mendapat kotak masuk @gmail.com. Untuk menyewa kotak masuk @icloud.com, tambahkan "domain": "icloud.com" ke body POST /v1/orders. Jika domain dihilangkan, artinya gmail.com — integrasi yang sudah ada tetap berjalan tanpa perubahan. iCloud hanya tersedia untuk layanan yang field icloud-nya bukan null di GET /v1/services; harga iCloud bisa berbeda dari harga Gmail (lihat icloud.price). Setiap respons pesanan menyertakan field domain yang menunjukkan jenis kotak masuk yang dipakai pesanan tersebut. Beberapa layanan juga menawarkan kotak masuk @gmx.com (saat ini hanya Pokemon, kode pkm, gudang 2): kirim "domain": "gmx.com" — sebuah layanan menawarkannya bila offers[]-nya di GET /v1/services memiliki entri dengan domain = "gmx.com"; layanan lain ⇒ DOMAIN_UNAVAILABLE. gmx.com tidak tersedia untuk paket.
Gudang: satu layanan, beberapa sumber kotak masuk
Sebuah layanan dapat dijual dari beberapa gudang, masing-masing dengan harga, stok, dan aturan kodenya sendiri (array offers[] di GET /v1/services). Kirim "warehouse": 2 saat membuat pesanan untuk memilih salah satunya; "warehouse": 99 ⇒ sistem memilih gudang termurah yang masih ada stok dan berpindah ke gudang berikutnya jika stoknya habis (ideal untuk layanan yang sering kehabisan stok, seperti Facebook dan Instagram); 0 ⇒ gudang termurah yang masih ada stok. Klien yang sudah ada tidak perlu perubahan: tanpa warehouse ⇒ gudang 1 seperti sebelumnya, dan price, stock, serta icloud menggambarkan gudang 1 (layanan tanpa gudang 1 ⇒ gudang termurah yang masih ada stok). Detail: lihat Gudang.
Sewa per paket (batch)
Paket dibeli di web (halaman Sewa per paket); lewat API Anda dapat melihat daftar paket dan membaca kode. Gmail (Gudang 1): setiap kotak masuk punya proxyUrl — link kode yang tidak memerlukan API key. iCloud (Gudang 3): kotak masuk pribadi; baca kode dengan GET /v1/batches/{id}/mails/{mailId}/codes atau buka readUrl di web. Detail: bagian Sewa per paket.
Beli akun Gmail masa aktif singkat (mail shop)
Selain menyewa kotak masuk, Anda juga bisa membeli akun Gmail secara utuh dari 2 gudang — gudang 1: email, kata sandi, cookies + link kotak masuk (juga dalam JSON); gudang 2: email, kata sandi + link login cepat; gudang 99 memilih yang termurah dulu (halaman Beli Gmail masa aktif singkat di web). API bisa melakukan semua yang dilakukan halaman itu: melihat gudang, harga, dan stok, membeli (Idempotency-Key wajib), memantau pengiriman, mengunduh akun, dan membaca email-nya. Detail: bagian Beli Gmail masa aktif singkat.
Biarkan pelanggan Anda melihat kode sendiri (halaman /otp)
Menjual ulang kotak masuk yang Anda sewa (lewat web atau API)? Aktifkan kode bagikan di halaman Akun, lalu kirim tautan https://otpgmail.net/id/otp#k7m2q9:[email protected] ke pelanggan (k7m2q9 = kode bagikan Anda) — mereka melihat kode terbaru email itu tanpa login dan tidak pernah melihat harga atau saldo Anda; halaman ini hanya melakukan tindakan gratis (tidak pernah memotong saldo Anda).
Link JSON untuk alat / bot (milik Anda atau pelanggan): pakai link berbentuk path https://otpgmail.net/id/otp/k7m2q9:[email protected] (membuka halaman /otp sama seperti link #) lalu tambahkan ?json ⇒ server mengembalikan JSON berisi data yang sama persis dengan yang ditampilkan halaman: cards[] — satu kartu untuk setiap layanan yang disewa pada email itu (email, serviceName, state, items[] = maksimal 5 kode / link terbaru, yang terbaru dulu) — serta serverTime. state: waiting menunggu kode pertama · listening menunggu kode baru · received sudah ada kode · needs_owner penjual harus memperpanjang · expired kedaluwarsa · banned kotak masuk dikunci. Beberapa email: pisahkan dengan koma (maksimal 50). Tanpa kunci API dan cookie, CORS terbuka; hanya baca (“Muat OTP baru” / “Sewa email ini lagi” tetap di halaman /otp). Batas anti-tebak sama seperti halaman: kode salah / email tanpa pesanan yang bisa ditampilkan ⇒ 404 NOT_FOUND; salah berulang kali atau lebih dari 30 kali baca per menit untuk satu email ⇒ 429 RATE_LIMITED beserta retryAfter (detik). Menambahkan ?json ke link /otp#… TIDAK mengembalikan JSON: browser tidak pernah mengirim bagian setelah # ke server.
curl "https://otpgmail.net/id/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
  }
}
Kode contoh lengkap di GitHub
Klien siap jalan untuk Python, Node.js, PHP, C# (.NET), dan curl — menyewa kotak masuk, menunggu kode, membatalkan otomatis untuk refund, dan berpindah antara iCloud dan Gmail saat stok habis: github.com/letoan0902/otpgmail-email-otp-api (Inggris) · thue-otp-gmail (Vietnam). Catatan Python: gunakan requests, bukan urllib polos (User-Agent bawaannya mendapat 403 dari CDN).
Webhook
Daripada terus menanyakan setiap pesanan, daftarkan satu URL dan terima POST bertanda tangan begitu pesanan mendapat kode baru atau dibatalkan / dikembalikan dananya — lihat Webhook.

Endpoint

GET/v1/balance
Mengembalikan saldo akun (dalam VND) untuk API key yang dipakai pada permintaan.
Contoh
curl "https://otpgmail.net/v1/balance" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "success": true,
  "data": {
    "balance": 25000
  }
}
Kemungkinan error: UNAUTHORIZED
GET/v1/services
Mendaftar layanan yang aktif — kode, nama, harga Gmail (dalam VND) dan stok Gmail saat ini, blok icloud berisi harga dan stok kotak masuk @icloud.com, serta array offers[]: setiap gudang yang menjual layanan itu, satu entri per gudang × jenis kotak masuk (harga, stok, jumlah kode, aturan). Data di-cache: stok diperbarui sekitar setiap 30–60 detik, harga lebih jarang.
icloud = null berarti layanan belum mendukung iCloud (penyedia tidak menawarkannya, atau dinonaktifkan sementara) — hanya Gmail yang bisa disewa. Untuk layanan dengan blok icloud, kirim "domain": "icloud.com" saat membuat pesanan untuk mendapatkan kotak masuk @icloud.com dengan harga icloud.price. price / stock / icloud.price menggambarkan gudang 1 — tempat pesanan tanpa warehouse diarahkan (layanan tanpa gudang 1 ⇒ gudang termurah yang ada stok); harga dan stok per gudang ada di offers[] (lihat Gudang). stock adalah angka yang diumumkan sumber (gudang 1: diperbarui kira-kira setiap 30 detik), bukan jaminan bahwa satu panggilan bisa menyewa sebanyak itu: kotak masuk diberikan satu per satu dan sumber bisa habis di tengah jalan — saat itu POST /v1/orders mengembalikan lebih sedikit pesanan disertai field partial. Gudang 2 dan 4 juga menampilkan angka dari sumbernya masing-masing.
Contoh
curl "https://otpgmail.net/v1/services" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
    }
  ]
}
Kemungkinan error: UNAUTHORIZED
POST/v1/orders
Membuat pesanan untuk kotak masuk yang menerima kode verifikasi — Gmail secara default, atau iCloud bila Anda mengirim domain: "icloud.com". Saldo Anda dipotong begitu penyedia memberikan kotak masuk. Mengembalikan daftar pesanan (satu, atau beberapa bila quantity > 1).
Pengiriman sebagian: quantity adalah batas maksimum
  • Kotak masuk diberikan satu per satu. Jika penyedia kehabisan di tengah jalan (misalnya gudang 1 saat itu tidak punya kotak masuk lagi untuk layanan tersebut), panggilan berhenti dan mengembalikan HTTP 200 beserta pesanan yang sudah dibuat — data bisa lebih sedikit dari quantity, dan Anda hanya dikenakan biaya untuk pesanan di data. Nilai stock di GET /v1/services tidak menjamin berapa yang didapat satu panggilan.
  • Dalam kasus itu respons juga membawa field tingkat atas partial di samping data (tidak ada jika semuanya terkirim — bot yang hanya membaca data tetap berjalan tanpa perubahan): requested, delivered, missing, dan reason = {code, message, retryable, retryAfterSec}, dengan code berupa error yang menghentikan panggilan (NO_MAILS_AVAILABLE, OUT_OF_STOCK, ALL_WAREHOUSES_EXHAUSTED, INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED…).
  • Kehabisan biasanya hanya sementara — di gudang 1 penyedia sering sudah punya kotak masuk lagi beberapa detik kemudian. Jika reason.retryable = true, tunggu reason.retryAfterSec detik (3–5 detik), lalu minta lagi kotak masuk yang kurang (missing) dengan Idempotency-Key BARU (key lama hanya mengulang respons yang sama ini); ulangi sampai semuanya didapat.
  • Tepat setelah pengiriman sebagian, panggilan berikutnya bisa mengembalikan 409 WAREHOUSE_UNAVAILABLE / OUT_OF_STOCK atau 503 NO_MAILS_AVAILABLE — itu normal: tetap beri jeda 3–5 detik antarpanggilan dan baru berhenti jika banyak panggilan berturut-turut gagal (misalnya 10 percobaan, sekitar satu menit); lalu coba lagi nanti, atau kirim "warehouse": 99 agar sistem pindah ke gudang lain. retryable = false (INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED…) ⇒ atasi dulu penyebabnya (isi saldo, atau tunggu pesanan yang masih terbuka selesai).
  • Respons error membawa dua field yang sama di dalam error: error.retryable dan error.retryAfterSec (misalnya 409 OUT_OF_STOCK ⇒ true, 5; 402 INSUFFICIENT_BALANCE ⇒ false, null) — lihat Kode error.
Header Idempotency-Key (sebuah UUID) wajib. Buat UUID baru untuk setiap sewa baru; saat mengulang setelah timeout, pakai ulang key yang SAMA agar tidak membuat pesanan ganda. Panggilan ini menunggu penyedia memberikan kotak masuk, sehingga biasanya memakan waktu sekitar 0,3 detik tetapi bisa sampai 10 detik pada jam sibuk (jarang) — atur timeout klien Anda sekitar 15 detik, jangan menyerah setelah 5 detik lalu mengulang terlalu cepat.
Parameter
NamaTipeWajibDeskripsi
servicestringYaKode layanan (dari GET /v1/services, mis. "ig", "tg", "fb")
quantitynumberTidakJumlah MAKSIMUM kotak masuk yang disewa (default 1; paling banyak 200 per permintaan) — jika gudang habis di tengah jalan, Anda mendapat lebih sedikit: hitung data[] dan baca partial
domainstringTidakJenis kotak masuk: "gmail.com" (default), "icloud.com", atau "gmx.com". Dihilangkan = gmail.com — integrasi yang sudah ada tidak perlu perubahan. iCloud hanya bisa disewa untuk layanan yang field icloud-nya bukan null di GET /v1/services; "gmx.com" hanya untuk layanan dengan offers[].domain = "gmx.com" (saat ini hanya Pokemon, kode pkm) — selain itu panggilan gagal dengan DOMAIN_UNAVAILABLE. Nilai lain ⇒ VALIDATION_ERROR.
warehousenumberTidakGudang tempat menyewa. Dihilangkan = GUDANG 1, persis seperti sebelum ada multi-gudang (layanan tanpa gudang 1 beralih ke yang termurah) — klien yang sudah ada tidak perlu perubahan. 0 = gudang termurah yang ada stok (hanya satu gudang yang dicoba). 1–4 = persis gudang tersebut (dari offers[].warehouse di GET /v1/services); jika gudang itu tidak menjual layanan/jenis kotak masuk ini, dijeda, atau kehabisan stok ⇒ WAREHOUSE_UNAVAILABLE. 99 = OTOMATIS: gudang yang ada stok dicoba dari yang termurah hingga termahal, berpindah saat satu gudang kehabisan stok atau gagal; tidak ada yang tersisa ⇒ ALL_WAREHOUSES_EXHAUSTED — disarankan untuk layanan yang sering kehabisan stok (Facebook, Instagram…). Lihat Gudang.
Contoh
# Buat Idempotency-Key acak untuk setiap pesanan BARU
# PAKAI ULANG key yang sama saat mengulang setelah timeout (mencegah pesanan ganda)
# domain: "gmail.com" (default, opsional) | "icloud.com" (sewa kotak masuk @icloud.com) | "gmx.com" (hanya layanan yang menawarkannya, mis. Pokemon)
# warehouse: 0 | 1–4 | 99 (opsional — hilangkan untuk tetap memakai gudang 1 seperti sebelumnya; 0 = gudang termurah yang ada stok; 99 = otomatis, berpindah saat gudang kehabisan stok; lihat offers[] di 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"}'
Contoh respons
{
  "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
    }
  ]
}
Contoh respons — pengiriman sebagian (quantity 5, 2 kotak masuk terkirim)
{
  "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
    }
  }
}
Kemungkinan error: 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, MAINTENANCE
GET/v1/orders/{id}
Mengembalikan pesanan menurut ID, termasuk kode yang sudah diterima sejauh ini (otp[]). Lakukan polling endpoint ini setiap 3–5 detik untuk mengambil kode selama pesanan berstatus waiting_code.
Array otp[] diurutkan menurut kedatangan: yang terlama dulu, TERBARU TERAKHIR — baca kode terbaru dengan otp[otp.length - 1]. Pada setiap panggilan, sistem memeriksa penyedia untuk pesanan yang masih terbuka dengan ritme masing-masing gudang (setiap beberapa detik) — memanggil lebih sering hanya mengembalikan data yang tersimpan. Setelah kode pertama, terus lakukan polling: kode berikutnya (jika layanan mengirim lebih dari satu) ditambahkan di akhir otp[] tanpa biaya tambahan selama masa pesanan — batas dan jendela waktunya ditampilkan sebelum Anda menyewa (maxCodes dan fee di offers[]); untuk memastikan sistem sedang menunggu kode baru, panggil POST /v1/orders/{id}/next-code (semua gudang; hanya gudang 2 di luar jendela gratisnya yang mengenakan biaya). Jika layanan hanya mengizinkan satu kode per kotak masuk, otp[] berhenti pada kode itu — respons tetap normal, tanpa error. Field warehouse memberi tahu gudang mana yang memasok kotak masuk (1–4); nextCodeFee adalah biaya untuk kode tambahan (pesanan gudang 2 dan 3; null untuk gudang 1 dan 4) — lihat Gudang. Sejak 2026-10-02 pesanan juga menyertakan nextCodeBlocked (true = kotak masuk tidak bisa menerima kode lagi) dan nextCodeBlockReason — alasannya: "email_banned" (email dikunci oleh layanan — sewa email baru), "activation_gone" (penyedia menutup kotak masuk), "hard_expired" (jendela kode telah berakhir), "max_codes" (jumlah kode maksimum telah diterima), null (tidak diblokir / pesanan lama tanpa alasan tercatat). Ini field tambahan — klien yang sudah ada tidak perlu perubahan. Melakukan polling banyak pesanan? Kirim hingga 100 ID per panggilan ke POST /v1/orders/status, bukan satu GET per pesanan — objek pesanan yang sama, satu permintaan pada rate limit Anda.
Parameter
NamaTipeWajibDeskripsi
idstringYaID pesanan (orderId yang dikembalikan saat pesanan dibuat)
Contoh
curl "https://otpgmail.net/v1/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND
POST/v1/orders/{id}/cancel
Membatalkan pesanan dan me-refund 100% harga. Hanya diizinkan selama pesanan berstatus waiting_code DAN belum ada kode yang diterima.
Pesanan yang sudah menerima kode (meski hanya satu) tidak dapat dibatalkan atau di-refund — berlaku untuk situs web maupun API. Pesanan dengan rules.cancellable = false (layanan stok sendiri seperti Cointiply) tidak dapat dibatalkan: begitu kotak masuk diberikan, tidak ada pembatalan atau refund — panggilan cancel mengembalikan ORDER_NOT_CANCELLABLE. Pesanan Gudang 3 dari layanan reguler (rules.cancellable = true) dapat dibatalkan seperti pesanan lain sebelum kode tiba.
Parameter
NamaTipeWajibDeskripsi
idstringYaID pesanan yang akan dibatalkan
Contoh
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/cancel" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json"
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR
POST/v1/orders/{id}/rerent
Menyewa kotak masuk yang SAMA lagi untuk pesanan layanan stok sendiri (provider = "inventory", mis. Cointiply): membuat pesanan BARU pada kotak masuk itu dengan jendela penerimaan baru, dengan harga rerentPriceVnd milik pesanan lama.
Hanya berfungsi bila pesanan lama memiliki canRerent = true (pesanan terbaru dari kotak masuk itu, yang jendela sebelumnya telah berakhir atau sudah menerima semua emailnya). Biaya langsung dipotong begitu panggilan berhasil — kirim Idempotency-Key dan pakai ulang saat mengulang agar Anda tidak pernah dikenai biaya dua kali. Responsnya { order, message }; order memiliki bentuk yang sama dengan GET /v1/orders/{id}. Gudang 2 (sejak 2026-10-09): pesanan yang dibatalkan, habis waktu, atau kedaluwarsa (kotak masuk tertutup) — panggil saat canRerent = true (pesanan terbaru dari email itu dan email tidak punya pesanan aktif lain). Sistem membuka kembali email YANG SAMA sebagai pesanan BARU (status = waiting_code) dengan harga rerentPriceVnd (saat ini 100% harga Anda) dan menunggu email selama 20 menit seperti pesanan Gudang 2 biasa — tanpa email, pesanan dibatalkan dan dikembalikan 100%. Email dikunci layanan ⇒ RERENT_EMAIL_BANNED; penyedia tidak bisa membukanya lagi ⇒ RERENT_MAIL_GONE; Gudang 2 sedang terganggu ⇒ PROVIDER_ERROR — ketiganya tidak memotong saldo.
Parameter
NamaTipeWajibDeskripsi
idstringYaID pesanan sebelumnya dari kotak masuk itu (yang memiliki canRerent = true)
Contoh
# PAKAI ULANG key yang sama saat mengulang setelah timeout (mencegah pesanan ganda)
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/rerent" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Idempotency-Key: $(uuidgen)"
Contoh respons
{
  "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"
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, RERENT_NOT_AVAILABLE, RERENT_EMAIL_BANNED, RERENT_MAIL_GONE, INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCE
POST/v1/orders/{id}/next-code
Meminta kode berikutnya pada kotak masuk yang SAMA — berlaku untuk pesanan dari SEMUA gudang (warehouse 1–4) yang sudah menerima kode pertamanya (status = completed). Gratis, kecuali pesanan gudang 2 yang jendela gratisnya sudah berakhir (nextCodeFee.feePriceVnd > 0): kirim confirmFee: true untuk membayar biayanya dan membuka jendela kode lain (hingga batas keras nextCodeFee.hardUntil). Bot Anda tidak perlu tahu gudangnya: terus lakukan polling GET /v1/orders/{id} — kode baru ditambahkan di akhir otp[] — dan panggil endpoint ini bila Anda ingin sistem menunggu kode baru.
Apa yang dilakukan panggilan ini di tiap gudang
  • Gudang 1: sistem menunggu kode berikutnya sendiri setelah setiap kode selama masa pesanan (rentExpiresAt) — panggilan mengembalikan 200 dan tidak mengubah apa pun; bila kotak masuk tidak bisa menerima kode lagi (batas kode tercapai, kotak ditutup, kedaluwarsa) ⇒ 409 dengan nextCodeBlockReason.
  • Gudang 2: membuka kembali kotak masuk di penyedia (reordered = true) lalu menunggu email selama 20 menit; gratis di dalam jendela gratis (nextCodeFee.feePriceVnd = 0), di luar jendela Anda harus mengirim confirmFee = true (tanpa itu ⇒ 409 NEXT_CODE_FEE_CONFIRM, tidak ada biaya ditarik). Biaya hanya ditahan bila kode baru tiba — tidak ada kode dalam 20 menit ⇒ dikembalikan otomatis. Setelah setiap kode, sistem terus menunggu kode berikutnya 25 menit lagi (listening = true, listenUntil).
  • Gudang 3 (layanan biasa): membuka kembali jendela mendengarkan singkat, gratis di dalam jendela gratis (nextCodeFee.freeUntil); setelah itu ⇒ 409 — gunakan POST /v1/orders/{id}/rerent. Layanan inventaris sendiri seperti Cointiply (rules.cancellable = false) tidak punya panggilan ini (409): setiap email muncul sendiri selama jendela.
  • Gudang 4: membaca ulang pesanan di penyedia dan menunggu kode baru, gratis hingga rentExpiresAt.
200 = sistem sedang menunggu kode baru untuk pesanan ini (listening = true, listenUntil = kapan penantian berhenti; reordered = true bila kotak masuk baru dibuka kembali; chargedVnd bila ada biaya ditarik), atau kode baru tiba tepat saat Anda memanggil (sudah ada di order.otp). 409 = penantian baru tidak bisa dibuka — baca error.code; alasan kotak masuk tidak menerima kode lagi ada di order.nextCodeBlockReason (GET /v1/orders/{id}). Pesanan yang masih menunggu kode pertama (waiting_code) ⇒ 409, belum perlu dipanggil. Kirim Idempotency-Key pada panggilan berbayar (gunakan lagi saat mencoba ulang agar tidak terpotong dua kali). Penyedia tidak dapat dihubungi ⇒ 502 PROVIDER_ERROR, coba lagi nanti. Kode error 409 untuk panggilan ini: NEXT_CODE_FEE_CONFIRM (gudang 2 memerlukan biaya — panggil lagi dengan confirmFee: true), EMAIL_BANNED (email dikunci oleh layanan — sewa yang baru; error.details.blockReason = "email_banned"), ORDER_EXPIRED (kedaluwarsa, kotak masuk ditutup, batas kode tercapai, atau layanan tidak punya "kode berikutnya" — alasannya ada di order.nextCodeBlockReason), ORDER_NOT_CANCELLABLE (pesanan belum menerima kode pertamanya). Bot yang menganggap setiap 409 dari panggilan ini sebagai "tidak ada kode lagi" tetap berjalan tanpa perubahan. refresh: true (gudang 2): pertama memeriksa email baru (yang baru tiba langsung dikirim, tidak dibuka ulang), lalu membuka kembali kotak masuk di penyedia — di dalam jendela gratis penantian dimulai lagi selama 20 menit; di luar jendela batas penantian (listenUntil) tetap dan tidak ada biaya. Respons menambahkan refreshed (true = baru dibuka ulang) dan refreshAvailableAt (detik unix; sebelum waktu itu penyegaran berikutnya hanya memeriksa email; null = boleh sekarang). Gudang lain: tidak berpengaruh (refreshed = false). Tanpa refresh panggilan berjalan persis seperti sebelumnya.
Parameter
NamaTipeWajibDeskripsi
idstringYaID pesanan (orderId) — gudang mana pun
confirmFeebooleanTidaktrue = Anda setuju membayar nextCodeFee.feePriceVnd untuk membuka jendela kode lain. Tidak diperlukan selama feePriceVnd = 0.
refreshbooleanTidaktrue = gudang 2, pesanan menunggu kode baru (listening = true) tetapi kode tidak kunjung datang: buka kembali kotak masuk di penyedia sekarang juga (segarkan penantian) — penantian lama mungkin sudah berhenti menerima email di sisi penyedia. Gratis; paling sering sekali per 5 dtk per pesanan. Pesanan tidak sedang menunggu ⇒ sama seperti panggilan biasa.
Contoh
# Gratis (nextCodeFee null atau feePriceVnd = 0 — semua gudang kecuali gudang 2 setelah jendela gratisnya): tidak perlu confirmFee
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
  -H "Authorization: Bearer og_xxx..."

# Gudang 2 setelah jendela gratisnya: kirim confirmFee = true untuk membayar nextCodeFee.feePriceVnd
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}'
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, NEXT_CODE_FEE_CONFIRM, INSUFFICIENT_BALANCE, EMAIL_BANNED, ORDER_EXPIRED, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR, DUPLICATE_REQUEST, MAINTENANCE

Status banyak pesanan dalam satu panggilan

Reseller dan bot yang menyewa banyak kotak masuk sebaiknya tidak mengirim satu GET /v1/orders/{id} per pesanan: POST /v1/orders/status membaca hingga 100 pesanan per panggilan dengan objek pesanan yang sama, dan GET /v1/orders menampilkan daftar pesanan Anda dari data tersimpan.
  • Lakukan polling setiap 3–5 detik dengan POST /v1/orders/status, hingga 100 ID pesanan per panggilan — setiap panggilan dihitung satu permintaan pada rate limit Anda.
  • Setiap panggilan, sistem memeriksa penyedia untuk paling banyak 20 pesanan terbuka dalam daftar (yang menunggu kode pertama lebih dulu, lalu yang paling lama tidak diperiksa) dan mengembalikan sisanya dari data tersimpan; sisanya diperiksa pada panggilan berikutnya. Satu panggilan menjawab dalam sekitar 3 detik.
  • Keluarkan pesanan dari daftar Anda begitu sudah final: cancelled (dana dikembalikan), atau completed jika Anda tidak butuh kode lagi.
  • ID yang tidak ada atau milik akun lain dikembalikan di missing (tanpa 404) — keluarkan juga.
  • Setelah restart, ambil kembali semua pesanan terbuka dengan GET /v1/orders?status=waiting_code (ikuti nextCursor), lalu lanjutkan polling dengan POST /v1/orders/status.
POST/v1/orders/status
Mengembalikan hingga 100 pesanan Anda dalam satu panggilan. Setiap elemen sama persis dengan yang dikembalikan GET /v1/orders/{id} untuk pesanan itu (status, otp[] berisi semua kode yang diterima, warehouse…). Gunakan untuk polling banyak pesanan terbuka sekaligus.
data mengikuti urutan ids; ID duplikat diabaikan. ID yang tidak ada atau milik akun lain dicantumkan di missing (tanpa 404). Untuk pesanan terbuka, sistem memeriksa penyedia seperti GET /v1/orders/{id}, tetapi paling banyak untuk 20 pesanan per panggilan — yang menunggu kode pertama lebih dulu, lalu yang paling lama tidak diperiksa — dan mengembalikan sisanya dari data tersimpan (diperiksa pada panggilan berikutnya); satu panggilan menjawab dalam sekitar 3 detik. Satu panggilan dihitung satu permintaan pada rate limit Anda, berapa pun jumlah ID-nya. Lebih dari 100 ID, daftar kosong, atau ID yang bukan string ⇒ 400 VALIDATION_ERROR.
Parameter
NamaTipeWajibDeskripsi
idsstring[]YaID pesanan (orderId) — 1 sampai 100 per panggilan
Contoh
# ID pesanan yang sedang Anda tunggu (orderId dari POST /v1/orders) — hingga 100 per panggilan
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"]}'
Contoh respons
{
  "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"
  ]
}
Kemungkinan error: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED
GET/v1/orders
Menampilkan daftar pesanan Anda, yang terbaru lebih dulu, hanya dari data tersimpan — tidak meminta kode baru ke penyedia (itu tugas POST /v1/orders/status). Filter menurut status dan waktu dibuat; pindah halaman dengan nextCursor.
Kirim nextCursor sebagai cursor untuk mendapatkan halaman berikutnya; nextCursor = null ⇒ halaman terakhir. Halaman tidak pernah mengulang atau melewatkan pesanan, bahkan jika banyak pesanan dibuat pada detik yang sama. Status internal tidak pernah ditampilkan (hanya waiting_code, completed, cancelled, dan queued). Setelah bot Anda restart: GET /v1/orders?status=waiting_code&limit=200, ikuti nextCursor, lalu lakukan polling pesanan-pesanan itu dengan POST /v1/orders/status.
Parameter
NamaTipeWajibDeskripsi
statusstringTidakHanya status ini: waiting_code | completed | cancelled | queued (kosong ⇒ semuanya)
sincenumberTidakHanya pesanan yang dibuat pada atau setelah waktu unix ini (detik)
limitnumberTidakPesanan per halaman: 1–200 (default 50)
cursorstringTidaknextCursor dari halaman sebelumnya, apa adanya
Contoh
# Setelah restart: kumpulkan semua pesanan yang masih menunggu kode, halaman demi halaman
curl "https://otpgmail.net/v1/orders?status=waiting_code&limit=200" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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"
}
Kemungkinan error: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED

Gudang

Setiap entri offers[] adalah satu penawaran = (layanan, gudang, jenis kotak masuk). Hanya gudang yang sedang menjual yang dicantumkan; gudang yang stoknya sementara habis tetap ada di daftar dengan stock = 0. Nomor gudang bersifat tetap: 1, 2, 3, 4 — kirim nomor yang sama di parameter warehouse saat membuat pesanan.
Klien yang sudah ada tidak perlu perubahan. Tanpa warehouse ⇒ perilaku sama seperti sebelumnya (gudang 1: menunggu 25 menit, kode tambahan masuk otomatis selama masa pesanan 24 jam bila layanan mengirim lebih dari satu kode); kirim 0 ⇒ gudang termurah yang ada stok; kirim 99 ⇒ gudang otomatis, berpindah saat satu gudang habis (lihat tepat di bawah); setiap field yang sudah ada tetap seperti semula — hanya ditambah field baru (offers[], warehouse, nextCodeFee, listening, nextCodeBlocked…).
Gudang otomatis: warehouse = 99 (disarankan untuk layanan yang sering kehabisan stok)
Kirim "warehouse": 99 dan sistem menyewa dari gudang termurah yang ada stok; jika gudang itu kehabisan stok atau gagal, sistem berpindah ke gudang berikutnya dalam panggilan yang sama. Ideal untuk layanan yang sering kehabisan stok, seperti Facebook dan Instagram — bot Anda tidak perlu lagi membaca offers[] dan mengulang gudang demi gudang.
  • Urutan yang dicoba: harga dari rendah ke tinggi (mis. 1.500 → 1.600 → 1.700 VND); harga sama ⇒ nomor gudang yang lebih kecil lebih dulu.
  • Gudang yang melaporkan stok nol (stock = 0 di offers[]) tidak pernah dipanggil — tidak ada waktu yang terbuang.
  • Berpindah saat gudang saat ini kehabisan stok atau gagal (OUT_OF_STOCK, NO_MAILS_AVAILABLE, WAREHOUSE_UNAVAILABLE, PROVIDER_ERROR).
  • Error lain langsung dikembalikan, tanpa mencoba gudang berikutnya: INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED, QUANTITY_EXCEEDED, MAINTENANCE, SERVICE_DISABLED, DOMAIN_UNAVAILABLE…
  • quantity > 1: jika gudang pertama kekurangan stok, sisanya diambil dari gudang berikutnya — pesanan bisa mencakup beberapa gudang; setiap pesanan memuat warehouse dan price dari gudang yang menerbitkannya (dan mengikuti aturan kode gudang itu).
  • Jika semua gudang sudah dicoba dan tidak ada kotak masuk yang bisa disewa ⇒ ALL_WAREHOUSES_EXHAUSTED (HTTP 409, tidak ada yang dikenakan) — coba lagi nanti. Jika hanya sebagian kotak masuk yang berhasil disewa, Anda tetap mendapat 200 dengan pesanan-pesanan tersebut.
Contoh: sewa Gmail Facebook dengan warehouse = 99
# warehouse: 99 — gudang termurah yang ada stok; stok habis atau gagal ⇒ otomatis ke gudang berikutnya
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}'
Field offers[]
FieldTipeDeskripsi
warehouse1 | 2 | 3 | 4Nomor gudang: 1, 2, 3, atau 4.
domainstringJenis kotak masuk pada penawaran: gmail.com, icloud.com, atau gmx.com (domain tambahan — hanya beberapa layanan, saat ini Pokemon).
pricenumberHarga satu kotak masuk dari gudang ini (VND).
stocknumberSisa kotak masuk. 0 = habis — menyewa dari gudang 1 atau 2 mengembalikan WAREHOUSE_UNAVAILABLE; gudang 4 tetap dicoba (sumbernya sering tetap memberi email meski melaporkan 0) dan mengembalikan OUT_OF_STOCK bila benar-benar kosong.
maxCodesnumber | nullJumlah kode maksimum yang bisa diterima satu kotak masuk; null = tidak dibatasi / tidak diketahui (lihat fee).
rulesobject | nullAturan jendela untuk gudang 3 (windowMinutes, maxCodes, cancellable, refundOnTimeout, rerentRatio); null untuk gudang 1, 2, dan 4.
feeobject | nullJendela kode tambahan: freeHours jam gratis sejak sewa, lalu setiap perpanjangan dikenai paidPct% dari harga sewa, hingga maxHours jam sejak sewa (null = tidak dibatasi). fee = null: tanpa biaya — kode tambahan tidak dikenai biaya selama masa pesanan bila layanan mengirim lebih dari satu kode (hingga maxCodes), seperti di gudang 1.
isDefaultbooleantrue untuk gudang termurah yang ada stok (harga sama ⇒ nomor gudang yang lebih kecil) — yang dipakai dengan warehouse = 0, dan yang pertama dicoba dengan 99.
Aturan menurut gudang
AturanGudang 1Gudang 2Gudang 3Gudang 4
Menunggu kode pertama25 menit20 menit25 menit ¹Paling lama 25 menit (lebih singkat di beberapa sumber — batas sebenarnya ada di codeDeadlineAt pesanan)
Tanpa kodeDibatalkan otomatis, refund 100%Dibatalkan otomatis, refund 100%Dibatalkan otomatis, refund 100% ¹Dibatalkan otomatis, refund 100%
Batal sebelum ada kodeYaYaYa ¹Ya
Kode per kotak masukSesuai maxCodesTak terbatasTak terbatas ¹Sesuai maxCodes (biasanya null = tidak dibatasi; 1 untuk beberapa penawaran)
Kode tambahanTanpa biaya tambahan selama masa pesanan 24 jam bila layanan mengirim lebih banyak kode; otomatis (tanpa panggilan tambahan). Kotak masuk biasanya tetap terbuka sekitar 25 menit setelah kode terakhir.Gratis selama fee.freeHours jam (saat ini Gmail 6 jam, iCloud 12 jam); setelah itu setiap perpanjangan dikenai fee.paidPct% dari harga lewat next-code, hingga fee.maxHours (Gmail 24 jam, iCloud 10 hari)Tanpa biaya tambahan selama 24 jam; setelah itu sewa kotak masuk yang sama lagi (rerent) dengan 50% dari hargaTanpa biaya tambahan selama masa pesanan 24 jam, seperti gudang 1 — kode baru muncul saat Anda membaca pesanan; next-code langsung membacanya ulang.
¹ Layanan yang hanya dijual dari stok sendiri, seperti Cointiply (provider = "inventory", rules.cancellable = false), mengikuti rules: hingga rules.maxCodes email dalam rules.windowMinutes menit, tanpa pembatalan, dan tanpa refund jika waktu habis tanpa ada email.
nextCodeFee — biaya untuk kode tambahan (pesanan gudang 2 dan 3)
Disertakan di GET /v1/orders/{id} dan setiap respons pesanan; null untuk pesanan gudang 1 dan 4 (kode tambahan gratis). Gudang 2: panggil POST /v1/orders/{id}/next-code saat Anda butuh kode lain — jika feePriceVnd di atas 0 Anda harus mengirim confirmFee = true. Gudang 3 (layanan biasa): setelah jendela gratis berakhir, feePriceVnd adalah harga menyewa ulang kotak masuk yang sama (rerent).
FieldTipeDeskripsi
feePriceVndnumberJumlah yang dikenakan jika Anda memanggil next-code sekarang; 0 = masih dalam jendela gratis.
freeUntilnumber | nullAkhir jendela gratis saat ini (detik unix).
hardUntilnumber | nullBatas keras (detik unix) — tidak ada kode lagi setelah waktu ini.
paidPctnumber% dari harga sewa yang dikenakan untuk setiap jendela tambahan.
freeHoursnumberJumlah jam gratis dalam satu jendela kode (gudang 2: Gmail 6, iCloud 12; gudang 3: 24).
Contoh: daftar gudang, lalu sewa dari salah satunya
# warehouse: 0 | 1–4 | 99 (opsional — hilangkan untuk tetap memakai gudang 1 seperti sebelumnya; 0 = gudang termurah yang ada stok; 99 = otomatis, berpindah saat gudang kehabisan stok; lihat offers[] di 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}'
Contoh pesanan gudang 2 (GET /v1/orders/{id})
{
  "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
  }
}

Sewa per paket (batch)

Paket dibeli di web (halaman Sewa per paket) — lewat API Anda dapat melihat daftar paket dan membaca kode. Domain email menentukan gudang: Gmail = Gudang 1, iCloud = Gudang 3 (kotak masuk pribadi).
  • Sebuah paket disimpan selama 24 jam sejak pembelian — baca kode secara gratis selama waktu itu, tanpa pembatasan jumlah kode.
  • Kotak masuk yang tidak menerima kode tidak di-refund; hanya kekurangan jumlah yang di-refund (otomatis).
  • Gudang 1 (Gmail): setiap kotak masuk punya proxyUrl — sebuah GET mengembalikan {"status":1,"code":"482913","all_codes":[...]} tanpa API key (link itulah rahasianya — jaga kerahasiaannya). Endpoint kode di bawah juga berfungsi.
  • Gudang 3 (iCloud): proxyUrl = null — baca kode dengan endpoint di bawah atau buka readUrl (halaman web). Setiap kotak masuk terikat ke akun Anda untuk layanan itu; setelah kedaluwarsa Anda masih bisa menyewanya lagi di web.
  • Email tanpa kode yang bisa diekstrak (email selamat datang, newsletter…) dikembalikan apa adanya di code dengan isRaw: true (HTML atau teks, hingga 20.000 karakter).
Field sebuah kotak masuk dalam paket (mails[])
FieldTipeDeskripsi
idnumberID kotak masuk — parameter {mailId} pada endpoint kode
emailstringAlamat email
urlstringGudang 1: = proxyUrl · gudang 3: = readUrl (dipertahankan untuk skrip lama / file ekspor)
proxyUrlstring | nullGudang 1: link kode yang tidak memerlukan login · gudang 3: null
readUrlstringHalaman web untuk membaca kode paket
codeCountnumberJumlah kode yang diterima
lastCodestring | nullKode terbaru (email mentah: 300 karakter pertama — teks lengkap ada di endpoint kode)
lastIsRawbooleantrue = kode terbaru adalah email mentah
checkedAtnumber | nullTerakhir kali sistem memeriksa penyedia untuk kotak masuk ini (detik unix; null = belum pernah)
GET/v1/batches
Paket Anda, yang terbaru lebih dulu (dipaginasi dengan page / pageSize, hingga 100). Setiap paket memiliki warehouse (1 = Gmail, 3 = iCloud), status (active | expired | failed), expiresAt, codeCount, dan readUrl.
Parameter
NamaTipeWajibDeskripsi
pagenumberTidakHalaman (default 1)
pageSizenumberTidakJumlah paket per halaman (default 20, maks 100)
Contoh
curl "https://otpgmail.net/v1/batches?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED
GET/v1/batches/{id}
Satu paket beserta mails[]-nya — ambil id setiap kotak masuk untuk membaca kodenya.
Paket Gmail (gudang 1): proxyUrl setiap kotak masuk berfungsi di skrip Anda persis seperti sebelumnya. Paket iCloud (gudang 3): proxyUrl = null, baca kode dengan endpoint di bawah. Paket iCloud yang sudah kedaluwarsa menyertakan rerentPriceVnd — harga untuk menyewa satu kotak masuk lagi secara satuan (dilakukan di web).
Parameter
NamaTipeWajibDeskripsi
idstringYaID paket (tampil di halaman Sewa per paket dan di readUrl)
Contoh
curl "https://otpgmail.net/v1/batches/BATCH_ID" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
      }
    ]
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND
GET/v1/batches/{id}/mails/{mailId}/codes
Baca kode dari satu kotak masuk dalam paket: sistem memeriksa penyedia jika kotak masuk sudah waktunya, lalu mengembalikan SETIAP kode yang diterima, yang terlama dulu — kode terbaru ada di urutan terakhir codes[].
Lakukan polling setiap 5–10 detik selagi menunggu (memanggil lebih sering hanya mengembalikan data tersimpan — polledAt tidak berubah). Email tanpa kode yang bisa diekstrak dikembalikan apa adanya di code dengan isRaw: true. Paket yang sudah kedaluwarsa tetap mengembalikan kode tersimpannya tetapi tidak diperiksa lagi.
Parameter
NamaTipeWajibDeskripsi
idstringYaID paket
mailIdnumberYaID kotak masuk (mails[].id)
poll0 | 1Tidak0 = hanya baca kode tersimpan, jangan periksa penyedia (default 1)
Contoh
# Panggil lagi setiap 5–10 detik selagi menunggu; kode terbaru ada di urutan terakhir codes[]
curl "https://otpgmail.net/v1/batches/BATCH_ID/mails/MAIL_ID/codes" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
    }
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, RATE_LIMITED

Buy 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
WarehouseEach account deliversDefault priceStock
1gm_cookieemail · password · cookies · inboxUrl (inbox link) · inboxJsonUrl (emails as JSON) · twoFaUrl (2FA link — just the 6 digits)3,300 VNDA number — published by the source, refreshed every minute. The source checks that accounts are live at purchase time, so you may get fewer — the shortfall is refunded (a partial order; warehouse 99: the rest goes to the next warehouse)
2gm_loginemail · password · loginUrl (quick login link — open it in a desktop browser) · otpUrl (Vinted OTP link — when delivers lists it)6,000 VNDA REFERENCE number from a source sharing the same stock (refreshed every minute; null when it cannot be read) — may be off by a few, never blocks a purchase; a shortfall becomes a partial order with the rest refunded
99As the warehouse that supplied each account (items[].warehouse)Price of the cheapest warehouse on sale (auto.priceVnd)Buys the cheapest warehouse first (auto.order); if it runs out midway the rest goes to the next warehouse at that warehouse's price (the difference is charged — refunded instead if your balance is short); every warehouse out ⇒ MAILSHOP_OUT_OF_STOCK, nothing charged
Masuk ke akun gudang 1 (tanpa email pemulihan)
  • Akun gudang 1 (gm_cookie) datang tanpa email pemulihan — Anda menerima email, kata sandi, cookies, dan link kotak masuk (inboxUrl / inboxJsonUrl), tidak lebih.
  • Cara masuk yang disarankan: impor cookies (cookies, JSON) ke browser — dengan ekstensi impor cookie atau fitur impor cookie di browser antidetect Anda — lalu buka Gmail: sesi sudah dalam keadaan masuk, biasanya tanpa diminta kata sandi atau verifikasi.
  • Hanya butuh kode atau email? Baca lewat inboxUrl (halaman web) atau inboxJsonUrl (JSON) — tanpa API key dan tanpa masuk ke Google.
  • Masuk dengan kata sandi di perangkat atau IP baru bisa membuat Google meminta verifikasi (email pemulihan, telepon, atau kode) — akun ini tidak punya apa pun untuk melewatinya, jadi gunakan cookies atau link kotak masuk.
  • Menjual kembali? Berikan kepada pelanggan Anda cookies dan link kotak masuk — unduhan .txt (GET /v1/mail-shop/orders/{id}/download) memuat email|password|cookies|inboxUrl dalam satu baris — dan minta mereka masuk dengan cookies, bukan kata sandi.
  • Akun gudang 2 (gm_login) sebagai gantinya dilengkapi link login cepat (loginUrl) — buka di browser komputer untuk masuk.
3 steps
  1. 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, call GET /v1/mail-shop/quote.
  2. Buy: POST /v1/mail-shop/orders with {"warehouse": 1 | 2 | 99, "quantity": N} + header Idempotency-Key (a new UUID per purchase). The response is the order, usually already completed with items[].
  3. Get the accounts: read items[] (table below) or download the TXT GET /v1/mail-shop/orders/{id}/download. status = pending ⇒ poll GET /v1/mail-shop/orders/{id} every 5 seconds until completed / partial / failed.
# 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..."
Fields of an account (items[])
FieldTipeDeskripsi
lineNonumberPosition in the order (1, 2, …) — the {lineNo} of the inbox endpoints
warehouse1 | 2Warehouse that supplied this account
emailstringGmail address
passwordstringPassword
recoverystring?Recovery email — only when the source provides one; warehouse 1 (gm_cookie) accounts have NONE (sign in with the cookies, see the sign-in notes above)
cookiesstring | nullWarehouse 1: login cookies (JSON)
inboxUrlstring | nullWarehouse 1: inbox page without an API key (https://otpgmail.net/mail/{code}); null = link turned off
inboxJsonUrlstring | nullWarehouse 1: the same link + ?json ⇒ emails as JSON (section below)
twoFaUrlstring | nullWarehouse 1: the 2FA LINK (https://otpgmail.net/2fa/{code} — same code + expiry as inboxUrl): GET / open it in a browser ⇒ JUST the 6-digit 2FA code (section below). null = link turned off. The .txt file has this column when downloaded with ?twofa=1
inboxLinkExpiresAtnumber | nullWarehouse 1: when the link expires (unix seconds); null = no limit
loginUrlstring | nullWarehouse 2: quick login link
otpUrlstring | nullWarehouse 2: the account's Vinted OTP link — open it in a browser to see the code, ?json ⇒ JSON (section below). null = this account has no link
otpService"vinted" | nullWarehouse 2: the service of otpUrl
rawLinestringThe whole account on one line (cookies compacted) — copy it as is
Read the inbox as JSON (inboxJsonUrl)
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"
# 424819
Order statuses
StatusDeskripsi
pendingBeing delivered — accounts that arrived are in items[]; poll again in 5 seconds
completedFully delivered. Final.
partialDelivered short — the shortfall was refunded (refundVnd; message = the reason + the source's own words when it gave any). Final.
failedNothing delivered — fully refunded (message as above). Final.
refundedRefunded by an admin (refundVnd). Final.
Rules
  • Money: chargedVnd = Σ route[].quantity × unitPriceVnd (a 99 order split across warehouses ⇒ two legs in route). Shortfalls are refunded automatically (refundVnd). Delivered accounts are not refunded — the only warranty is the warehouse's terms (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 ⇒ 429 RATE_LIMITED.
  • Common errors: MAILSHOP_OUT_OF_STOCK 409 (details.stock = how many you can buy now) · MAILSHOP_QTY_LIMIT 400 (details.max) · INSUFFICIENT_BALANCE 402 · MAILSHOP_DISABLED 503 (warehouse / section paused) · MAILSHOP_SUPPLIER_ERROR 502 (the source refused at once — a failed order was recorded and refunded, details.orderId) · VALIDATION_ERROR 400 (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.
Contoh
curl "https://otpgmail.net/v1/mail-shop/products" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
      ]
    }
  }
}
Kemungkinan error: UNAUTHORIZED
GET/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
NamaTipeWajibDeskripsi
warehouse1 | 2 | 99TidakMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringTidakInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberTidakNumber of accounts (default 1)
Contoh
curl "https://otpgmail.net/v1/mail-shop/quote?warehouse=99&quantity=10" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
      }
    ]
  }
}
Kemungkinan error: UNAUTHORIZED, VALIDATION_ERROR, MAILSHOP_PRODUCT_NOT_FOUND
POST/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
NamaTipeWajibDeskripsi
warehouse1 | 2 | 99TidakMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringTidakInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberTidakNumber of accounts (default 1)
Idempotency-KeystringYaHeader: a new UUID for every new purchase — reuse it only to retry that same purchase
Contoh
# Idempotency-Key wajib: UUID baru untuk setiap pembelian — pakai ulang key yang sama hanya saat mengulang setelah 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}'
Contoh respons
{
  "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"
      }
    ]
  }
}
Kemungkinan error: 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, MAINTENANCE
GET/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
NamaTipeWajibDeskripsi
pagenumberTidakPage (default 1)
pageSizenumberTidakOrders per page (default 20, max 100)
Contoh
curl "https://otpgmail.net/v1/mail-shop/orders?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED
GET/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
NamaTipeWajibDeskripsi
idstringYaOrder ID (data.id returned by the purchase)
Contoh
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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"
      }
    ]
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND
GET/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
NamaTipeWajibDeskripsi
idstringYaOrder ID (data.id returned by the purchase)
Contoh
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/download" \
  -H "Authorization: Bearer og_xxx..." \
  -o mail-ORDER_ID.txt
Contoh respons
email|password|cookies|inboxUrl
[email protected]|Pw7Xq!k3d|[{"domain":".google.com","name":"SID","value":"g.a000…"}]|https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR
GET/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
NamaTipeWajibDeskripsi
idstringYaOrder ID (data.id returned by the purchase)
lineNonumberYaAccount number within the order (items[].lineNo)
maxnumberTidakNumber of latest emails: 10 (default), 20, 50, 100 or 200
Contoh
# 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"
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR
GET/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
NamaTipeWajibDeskripsi
idstringYaOrder ID (data.id returned by the purchase)
lineNonumberYaAccount number within the order (items[].lineNo)
Contoh
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/status" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "live": true,
    "reason": null,
    "checkedAt": 1760000600
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR
GET/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
NamaTipeWajibDeskripsi
idstringYaOrder ID (data.id returned by the purchase)
lineNonumberYaAccount number within the order (items[].lineNo)
Contoh
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/2fa" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "success": true,
  "data": {
    "none": false,
    "code": "424819",
    "expiresIn": 24
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR

Siklus pesanan

StatusDeskripsi
Menunggu kodePesanan yang baru dibuat, menunggu kode pertama. Lakukan polling GET /v1/orders/{id} setiap 3–5 detik.
SelesaiKode pertama sudah tiba — status langsung berubah. Kode tambahan tetap bisa masuk jika layanan mengirim lebih dari satu (tanpa biaya tambahan selama masa pesanan; batas dan jendela waktu menurut gudang); teruslah polling dan setiap kode baru muncul di akhir otp[].
DibatalkanDikembalikan 100%. Terjadi bila Anda memanggil /cancel (sebelum kode tiba), atau bila tidak ada kode hingga codeDeadlineAt (paling lama 25 menit).
Catatan penting:
  • Lakukan polling GET /v1/orders/{id} setiap 3–5 detik selama pesanan berstatus waiting_code agar Anda segera menerima kodenya.
  • Begitu kode pertama tiba, status berubah menjadi completed seketika.
  • Tidak ada kode hingga codeDeadlineAt (paling lama 25 menit; gudang 2: 20 menit; beberapa sumber gudang 4 lebih singkat) ⇒ sistem membatalkan pesanan dan mengembalikan 100% (status = cancelled).
  • Array otp[] diurutkan terlama dulu, terbaru terakhir. Baca kode terbaru dengan otp[otp.length - 1] (Python: otp[-1]).
  • Pesanan completed terus menerima kode secara otomatis: terus lakukan polling GET /v1/orders/{id} dan setiap kode baru muncul di akhir otp[]. Kode tambahan tidak dikenai biaya selama masa pesanan bila layanan mengirim lebih dari satu; batas dan jendela waktunya ditampilkan sebelum Anda menyewa (maxCodes dan fee di offers[]; di gudang 1 kotak masuk biasanya tetap terbuka sekitar 25 menit setelah kode terakhir). Untuk meminta kode lain secara eksplisit (mis. login setelah mendaftar): POST /v1/orders/{id}/next-code — berlaku di semua gudang, dan hanya gudang 2 di luar jendela gratisnya yang mengenakan biaya.
  • Jika layanan hanya mengizinkan satu kode per kotak masuk, otp[] berhenti pada kode itu. Respons tetap normal, tanpa error — Anda yang menentukan kapan berhenti melakukan polling.
  • otp[].code biasanya berupa kode pendek (angka / huruf) atau sebuah tautan. Email yang kodenya tidak bisa diambil (email sambutan, buletin, format kode yang tidak biasa…) dikirim apa adanya (HTML atau teks) di otp[].code — anggap sebagai email utuh bila lebih dari 80 karakter, berisi baris baru, atau berupa HTML, lalu cari sendiri kode atau tautannya.
  • POST /v1/orders menunggu penyedia memberikan kotak masuk, sehingga biasanya memakan waktu ~0,3 detik tetapi bisa sampai 10 detik pada jam sibuk (jarang). Atur timeout klien ≈ 15 detik, jangan menyerah setelah 5 detik lalu mengulang terlalu cepat — dan saat Anda mengulang, pakai ulang Idempotency-Key yang sama.
  • Pesanan yang sudah menerima kode tidak dapat dibatalkan atau di-refund — panggilan cancel mengembalikan ORDER_NOT_CANCELLABLE.
  • Untuk menyewa kotak masuk @icloud.com, kirim "domain": "icloud.com" saat membuat pesanan (hilangkan untuk Gmail; bot yang sudah ada tidak perlu perubahan). Jika layanan tidak mendukung iCloud, atau iCloud dinonaktifkan sementara, Anda mendapat DOMAIN_UNAVAILABLE — beralih ke Gmail dan penyewaan langsung berhasil. Siklus pesanan, aturan pembatalan dan refund, serta cara Anda membaca kode persis sama dengan Gmail.
  • Layanan stok sendiri (provider = "inventory", mis. Cointiply — lihat GET /v1/services) hanya iCloud; setiap email tiba sebagai link di otp[].code, bersama kind, label, dan teks lengkap email di text. Sebuah pesanan menerima hingga rules.maxCodes email dalam rules.windowMinutes menit (hingga windowEndsAt), tidak dapat dibatalkan (ORDER_NOT_CANCELLABLE), dan jika waktu habis tanpa email, statusnya menjadi cancelled tanpa refund. Untuk terus menerima di kotak masuk yang sama, panggil POST /v1/orders/{id}/rerent selama canRerent bernilai true (harga: rerentPriceVnd). Semua layanan / pesanan lain memiliki provider = "standard" — sejak 2026-10-24 provider hanya punya dua nilai, "standard" | "inventory" (gudang sebenarnya ada di warehouse); bandingkan hanya dengan "inventory".
  • Pesanan gudang 2 (warehouse = 2): kode gratis selama fee.freeHours jam; begitu jendela gratis tertutup, kode baru berhenti datang — panggil POST /v1/orders/{id}/next-code dengan confirmFee = true (membayar nextCodeFee.feePriceVnd) untuk membuka jendela lain, hingga nextCodeFee.hardUntil. Pesanan gudang 3 untuk layanan biasa: kode gratis selama 24 jam, bisa dibatalkan sebelum kode tiba, setelah itu gunakan rerent. Pesanan gudang 4: kode baru masuk ke otp[] saat Anda membaca pesanan, gratis hingga rentExpiresAt; pada sumber satu kode, setelah kode pertama nextCodeBlockReason = "max_codes".
  • Banyak pesanan sekaligus? Lakukan polling hingga 100 per panggilan dengan POST /v1/orders/status — lihat bagian Status banyak pesanan dalam satu panggilan.

Rate limit

Batas berlaku per API key (akun bisa punya batas sendiri; jika tidak, berlaku batas bawaan situs):
  • 100 permintaan/detik — lonjakan singkat selama beberapa detik
  • 3000 permintaan/menit — total lalu lintas per menit
Melebihi batas mengembalikan HTTP 429 dengan header Retry-After: <seconds> dan body {"success":false,"error":{"code":"RATE_LIMITED",...}}. Tunggu sejumlah detik yang tertera di header sebelum mencoba lagi. Body error membawa jumlah detik yang sama di error.retryAfterSec (dengan error.retryable = true).

Kode error

Setiap error memiliki bentuk yang sama: {"success":false,"error":{"code":"...","message":"...","retryable":false,"retryAfterSec":null}}
Sejak 2026-10-17 setiap error /v1 juga memiliki error.retryable dan error.retryAfterSec (field tambahan — klien lama tidak perlu diubah; details tidak berubah). retryable = true ⇒ panggilan yang sama bisa berhasil nanti: tunggu retryAfterSec detik (bilangan bulat ≥ 1) lalu kirim lagi — stok habis / tidak ada kotak masuk / gudang dijeda / error penyedia ⇒ 5 detik; 429 RATE_LIMITED ⇒ header Retry-After; 409 DUPLICATE_REQUEST selama permintaan pertama dengan Idempotency-Key yang sama masih berjalan ⇒ 2 detik. retryable = false (retryAfterSec = null) ⇒ memanggil lagi tidak akan membantu sampai Anda memperbaiki penyebabnya (saldo, batas pesanan menunggu, parameter, API key…). partial.reason dari POST /v1/orders mengikuti aturan yang sama. Saat mendapat jawaban 409 stok habis (OUT_OF_STOCK, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED), tunggu retryAfterSec detik penuh sebelum mencoba lagi — mencoba lebih cepat biasanya hanya mengembalikan jawaban yang sama — dan periksa stock / offers[].stock di GET /v1/services sebelum menyewa lagi; pantau pesanan yang sudah Anda sewa dengan webhook alih-alih polling terus-menerus.
Contoh respons error
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
  }
}
Kode errorHTTPretryable · retryAfterSecArti
UNAUTHORIZED401false · nullAPI key tidak valid atau tidak ada.
ACCOUNT_LOCKED403false · nullAkun sedang dikunci — setiap permintaan dengan key-nya ditolak. Hubungi dukungan.
FORBIDDEN403false · nullAkun tidak lagi aktif saat penyewaan diproses (baru saja dikunci).
NOT_FOUND404false · nullTidak ada pesanan / paket / kotak masuk dengan ID ini di akun Anda (pesanan akun lain juga mengembalikan 404).
VALIDATION_ERROR400false · nullParameter permintaan tidak valid (tipe salah, field wajib hilang, dsb.).
INSUFFICIENT_BALANCE402false · nullSaldo akun terlalu rendah untuk membayar pesanan.
SERVICE_DISABLED409false · nullKode layanan tidak ada atau layanan sedang dinonaktifkan sementara. Ambil kode yang tepat dari GET /v1/services.
OUT_OF_STOCK409true · 5Layanan tidak memiliki kotak masuk yang tersedia (untuk jenis kotak masuk yang dipilih) pada saat permintaan.
DOMAIN_UNAVAILABLE409false · nullLayanan tidak menyediakan jenis kotak masuk yang dipilih — iCloud belum didukung / dinonaktifkan sementara, atau layanan tidak memiliki kotak masuk gmx.com. Kirim domain "gmail.com" (atau hilangkan field) untuk menyewa Gmail.
WAREHOUSE_UNAVAILABLE409true · 5 / falseGudang yang dipilih tidak menjual layanan/jenis kotak masuk ini, dijeda, atau baru saja habis. Pilih gudang lain dari offers[], atau kirim warehouse = 99 agar sistem memilih yang ada stok. Jika gudang itu (atau semua gudang yang menjual layanan ini) tidak tersedia untuk akun Anda, retryable = false — memanggil ulang tidak membantu, gunakan gudang lain.
ALL_WAREHOUSES_EXHAUSTED409true · 5Hanya dengan warehouse = 99: setiap gudang yang ada stok sudah dicoba (termurah dulu) dan semuanya kehabisan stok atau gagal — atau setiap gudang melaporkan stok nol. Tidak ada yang dikenakan; coba lagi nanti.
NO_MAILS_AVAILABLE503true · 5Penyedia tidak memiliki kotak masuk tersisa untuk layanan ini (habis sementara).
QUANTITY_EXCEEDED400false · nullquantity melebihi maksimum per permintaan (saat ini 200).
WAITING_LIMIT_REACHED429false · nullTerlalu banyak pesanan yang menunggu kode bersamaan (melebihi batas pesanan waiting_code serentak di akun Anda) — tunggu sebagian selesai, lalu sewa lagi.
ORDER_NOT_CANCELLABLE409false · nullPesanan tidak dapat dibatalkan — mungkin sudah memiliki kode, atau sudah tidak berstatus waiting_code. next-code juga mengembalikan kode ini bila pesanan belum menerima kode pertamanya.
RERENT_NOT_AVAILABLE409false · nullPesanan tidak dapat disewa lagi. Gudang 3: bukan pesanan terbaru dari kotak masuk itu, kotak masuk tidak lagi ditetapkan ke akun Anda, atau jendela penerimaan sebelumnya masih terbuka. Gudang 2: pesanan belum ditutup (masih bisa menerima kode — gunakan next-code), sudah terlalu lama untuk disewa lagi, email punya pesanan aktif lain, atau layanan sedang dijeda. Gudang 1 dan 4 tidak memiliki sewa lagi.
RERENT_EMAIL_BANNED409false · nullSewa lagi Gudang 2: penyedia melaporkan email dikunci oleh layanan (email banned) — tidak bisa disewa lagi; tidak ada biaya.
RERENT_MAIL_GONE409false · nullSewa lagi Gudang 2: penyedia tidak bisa membuka email ini lagi — sewa email baru; tidak ada biaya.
NEXT_CODE_FEE_CONFIRM409false · nullJendela gratis pesanan gudang 2 telah berakhir — panggil next-code lagi dengan confirmFee: true untuk membayar biayanya (nextCodeFee.feePriceVnd). Belum ada yang dikenakan.
EMAIL_BANNED409false · nullEmail telah dikunci oleh layanan (penyedia melaporkan email banned) — tidak dapat menerima kode lagi; sewa email baru. Sejak 2026-10-02 (sebelumnya ORDER_EXPIRED, HTTP 409 yang sama).
ORDER_EXPIRED409false · nullPesanan tidak dapat menerima kode lagi — batas waktunya lewat (rentExpiresAt / nextCodeFee.hardUntil), penyedia menutup kotak masuk, batas kode (maxCodes) tercapai, atau layanan tidak punya "kode berikutnya". Alasannya ada di order.nextCodeBlockReason.
RATE_LIMITED429true · = Retry-AfterLebih dari 100 permintaan/detik atau 3000 permintaan/menit pada API key Anda (akun bisa punya batas sendiri). Header Retry-After memberi tahu berapa detik Anda harus menunggu. Di gudang 2, POST /v1/orders dan /rerent juga mengembalikan kode ini bila dalam 60 menit terakhir akun membatalkan banyak pesanan gudang 2 tanpa kode sementara sangat sedikit yang mendapat kode — penyewaan di gudang 2 dijeda sampai tingkat pembatalan turun ("warehouse": 99 juga berhenti di sana dengan error ini — sementara itu pilih gudang lain). Facebook / Instagram (fb, fbb, ig) dikecualikan.
DUPLICATE_REQUEST409true · 2 / falseIdempotency-Key sudah dipakai dengan payload atau endpoint lain, atau permintaan pertama dengan key ini masih berjalan (coba lagi sebentar lagi). Buat key baru untuk membuat pesanan lain.
PROVIDER_ERROR502true · 5Error dari penyedia kotak masuk (sementara — ulangi setelah beberapa detik).
MAINTENANCE503false · nullSistem sedang dalam pemeliharaan — pembuatan dan pembatalan pesanan dijeda.
MAILSHOP_DISABLED503false · nullPembelian Gmail masa aktif singkat sedang dijeda, atau produk ini sedang dinonaktifkan — coba lagi nanti.
MAILSHOP_PRODUCT_NOT_FOUND404false · nullTidak ada produk dengan kode ini — ambil kode yang tepat dari GET /v1/mail-shop/products.
MAILSHOP_OUT_OF_STOCK409false · nullStok tidak cukup untuk jumlah ini pada harga saat ini — error.details.stock = jumlah akun yang bisa Anda beli sekarang.
MAILSHOP_QTY_LIMIT400false · nullquantity melebihi maksimum per pesanan (saat ini 500) — lihat error.details.max.
MAILSHOP_SUPPLIER_ERROR502false · nullSumber langsung menolak pembelian: sistem mencatat sebuah pesanan failed dan dananya sudah di-refund (error.details.orderId). Coba lagi nanti dengan Idempotency-Key baru.
INTERNAL500false · nullKesalahan server yang tidak terduga — coba lagi nanti; hubungi dukungan bila terus terjadi.

Webhook (notifikasi push)

Daftarkan satu URL per akun dan kami akan mengirim (POST) event JSON bertanda tangan ke URL itu begitu pesanan menerima kode baru (order.code) atau dibatalkan / dikembalikan dananya (order.cancelled). Webhook bersifat opsional dan mencakup semua pesanan akun Anda (web dan API). Pengiriman bersifat upaya terbaik — tetap lakukan polling jarang sebagai cadangan (lihat di bawah).
Siapkan dalam 3 langkah
  1. PUT /v1/webhook dengan URL Anda (dan opsional events) — simpan data.secret (64 karakter hex): diperlukan untuk memverifikasi setiap pengiriman. Memanggil PUT lagi tetap memakai secret yang sama.
  2. POST /v1/webhook/test — kami langsung mengirim event ping dan mengembalikan jawaban server Anda (ok, httpStatus, error).
  3. Endpoint Anda harus menjawab 2xx apa pun dalam 5 detik — jawab dulu, proses event-nya kemudian. Selain itu (3xx, 4xx, 5xx, waktu habis, galat koneksi) dihitung gagal dan dikirim ulang.
URL harus diawali http:// atau https:// (dengan sertifikat valid) dan dapat dijangkau dari internet: localhost, alamat privat, link-local, multicast dan alamat yang dicadangkan (10.x, 172.16–31.x, 192.168.x, 169.254.x, fc00::/7, fe80::/10…) serta server milik OTPGmail sendiri ditolak dengan VALIDATION_ERROR. Nama host di-resolve ulang sebelum setiap pengiriman. Pengalihan (redirect) tidak diikuti — daftarkan URL akhirnya.
Format permintaan
Setiap pengiriman adalah HTTP POST ke URL Anda dengan body JSON (UTF-8) dan header berikut:
HeaderNilai
Content-Typeapplication/json
User-AgentOTPGmail-Webhook/1
X-OTPGmail-EventNama event: order.code, order.cancelled atau ping
X-OTPGmail-DeliveryID unik event (UUID) — sama dengan deliveryId di body dan tidak berubah di setiap pengiriman ulang
X-OTPGmail-TimestampWaktu Unix (detik) saat percobaan ini ditandatangani — sama dengan sentAt di body
X-OTPGmail-Signaturesha256= + hex huruf kecil dari HMAC-SHA256 atas timestamp + "." + body mentah, dengan secret Anda sebagai kunci
Verifikasi tanda tangan
Hitung HMAC-SHA256 dengan secret Anda sebagai kunci atas X-OTPGmail-Timestamp + . + body permintaan mentah, dalam hex huruf kecil, lalu bandingkan dengan nilai setelah sha256= memakai perbandingan waktu-konstan. Kuncinya adalah string secret 64 karakter itu sendiri (byte UTF-8-nya — jangan didekode sebagai hex). Gunakan byte mentah: mem-parse lalu menyerialisasi ulang JSON akan mengubahnya. Tolak timestamp yang selisihnya lebih dari 5 menit dari jam Anda (perlindungan replay).
Contoh penerima (Python dan Node hanya memakai pustaka standar; perintah curl mengirim pengiriman uji bertanda tangan benar ke penerima Anda sendiri)
# Uji lokal: kirim ke penerima Anda pengiriman yang ditandatangani persis seperti milik kami
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"
Daftar event
EventDikirim saat
order.codePesanan menerima kode baru — satu event per kode, termasuk kode-kode berikutnya di kotak masuk yang sama. code adalah kode baru; otp memuat semua kode pesanan sampai saat itu, dari yang terlama (seperti GET /v1/orders/{id}; hanya kode baru jika body akan melebihi 64 KB).
order.cancelledPesanan dibatalkan atau dikembalikan dananya — sekali per pesanan: tidak ada kode sebelum batas waktu (timeout), dibatalkan oleh Anda (user) atau administrator (admin), atau dikembalikan karena gudang bermasalah (provider). refundVnd = VND yang dikembalikan ke saldo Anda (0 bila aturan layanan tidak memberi pengembalian).
pingHanya dari POST /v1/webhook/test.
Field event
FieldTipeDeskripsi
eventstringNama event (juga ada di header X-OTPGmail-Event)
deliveryIdstringID unik event (UUID, = X-OTPGmail-Delivery) — simpan untuk melewati duplikat
sentAtnumberWaktu Unix percobaan ini (= X-OTPGmail-Timestamp)
orderIdstringID pesanan — sama dengan di GET /v1/orders/{id}
servicestringKode layanan
warehouse1 | 2 | 3 | 4 | nullGudang 1–4 yang menyediakan kotak masuk; null jika pesanan tidak pernah mendapatkannya
domainstringDomain kotak masuk: gmail.com, icloud.com atau gmx.com
emailstring | nullAlamat kotak masuk yang disewa
codestringHanya order.code: kode baru — bisa juga berupa tautan atau email utuh, seperti otp[].code
receivedAtnumberHanya order.code: waktu Unix kode tiba
statusstringStatus pesanan saat itu: completed untuk order.code, cancelled untuk order.cancelled (pengembalian dana oleh admin untuk pesanan yang sudah punya kode tetap completed)
otparrayHanya order.code: semua kode sampai saat itu, [{code, receivedAt}], dari yang terlama
reasonstringHanya order.cancelled: timeout | user | admin | provider
refundVndnumberHanya order.cancelled: VND yang dikembalikan ke saldo Anda (0 = tanpa pengembalian)
Contoh body — order.code
{
  "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
    }
  ]
}
Contoh 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"
}
Contoh body — ping
{
  "event": "ping",
  "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f",
  "sentAt": 1760700000
}
Pengiriman ulang, jeda, dan riwayat
  • Pengiriman yang gagal dikirim ulang setelah 10 dtk, 1 mnt, 5 mnt, 30 mnt dan 2 jam (5 kali); jika yang terakhir juga gagal, event dibuang (failed).
  • 50 percobaan gagal berturut-turut (semua event) menjeda webhook: GET /v1/webhook menampilkan pausedAt, tidak ada yang dikirim dan event baru tidak dimasukkan ke antrean.
  • Untuk melanjutkan: panggil lagi PUT /v1/webhook (URL yang sama boleh) atau dapatkan POST /v1/webhook/test yang berhasil — event yang menunggu di antrean lalu dikirim.
  • GET /v1/webhook/deliveries menampilkan 100 pengiriman terakhir beserta status, jumlah percobaan, dan galat terakhir; riwayat disimpan 7 hari.
Duplikat dan urutan
Event yang sama bisa datang lebih dari sekali (misalnya saat jawaban 2xx Anda hilang) dan event bisa datang tidak berurutan (beberapa dikirim paralel). Simpan deliveryId yang sudah diproses dan lewati pengulangan; untuk satu pesanan, andalkan data terbarunya (otp, status), bukan urutan kedatangan.
Tetap lakukan polling sebagai cadangan
Webhook bisa terlambat (pengiriman ulang) atau terlewat (webhook dijeda, server Anda mati). Tetap lakukan polling jarang — misalnya GET /v1/orders/{id} setiap 30–60 detik untuk pesanan yang masih waiting_code — dan anggap objek pesanan dari API sebagai sumber kebenaran.
PUT/v1/webhook
Mendaftarkan atau memperbarui webhook akun Anda. Panggilan pertama membuatnya dan mengembalikan secret baru; panggilan berikutnya mempertahankan secret, menyalakan kembali webhook, dan melanjutkannya jika sedang dijeda. Tanpa events: kedua event untuk webhook baru, tidak berubah untuk webhook yang sudah ada. URL harus alamat publik — jika tidak, 400 VALIDATION_ERROR (alasannya di error.details.problem).
Parameter
NamaTipeWajibDeskripsi
urlstringYaEndpoint Anda (http:// atau https://, alamat publik, maksimal 2048 karakter)
eventsstring[]TidakEvent yang dikirim: ["order.code", "order.cancelled"] (bawaan: keduanya)
Contoh
# Simpan data.secret — diperlukan untuk memverifikasi setiap pengiriman
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"]}'
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED, VALIDATION_ERROR
GET/v1/webhook
Webhook akun Anda, termasuk secret, pausedAt, dan consecutiveFailures. data bernilai null jika belum ada webhook terdaftar.
Contoh
curl "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
  }
}
Kemungkinan error: UNAUTHORIZED
DELETE/v1/webhook
Menghapus webhook; event yang masih menunggu di antrean dibuang. Selalu 200 — data.deleted bernilai false jika tidak ada yang dihapus.
Contoh
curl -X DELETE "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "success": true,
  "data": {
    "deleted": true
  }
}
Kemungkinan error: UNAUTHORIZED
POST/v1/webhook/test
Mengirim event ping ke URL Anda saat itu juga dan melaporkan hasilnya: ok (server Anda menjawab 2xx), httpStatus, durationMs, error. Tes yang berhasil melanjutkan webhook yang dijeda. Maksimal 10 tes per menit; 503 MAINTENANCE selama webhook dimatikan di seluruh sistem.
Contoh
curl -X POST "https://otpgmail.net/v1/webhook/test" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "success": true,
  "data": {
    "ok": true,
    "httpStatus": 200,
    "durationMs": 143,
    "error": null,
    "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f"
  }
}
Respons 200 saat server Anda tidak menjawab 2xx
{
  "success": true,
  "data": {
    "ok": false,
    "httpStatus": 500,
    "durationMs": 88,
    "error": "HTTP 500",
    "deliveryId": "7d2b9f4e-1c6a-4e3b-8d5f-0a9c2e7b1f4d"
  }
}
Kemungkinan error: UNAUTHORIZED, NOT_FOUND, RATE_LIMITED, MAINTENANCE
GET/v1/webhook/deliveries
Pengiriman terbaru, yang terbaru dulu (disimpan 7 hari): status = queued (percobaan berikutnya pada nextAt) | delivered | failed | paused, beserta attempts, lastHttpStatus, dan lastError. Body event tidak dikembalikan.
Parameter
NamaTipeWajibDeskripsi
limitnumberTidakJumlah pengiriman, 1–100 (bawaan 100)
Contoh
curl "https://otpgmail.net/v1/webhook/deliveries?limit=20" \
  -H "Authorization: Bearer og_xxx..."
Contoh respons
{
  "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
    }
  ]
}
Kemungkinan error: UNAUTHORIZED