Versi 1.0.0
Dokumentasi API Reseller Nomerin
Beli nomor virtual penerima OTP langsung dari sistem Anda. Harga yang tersedia di API sama dengan harga mode Advanced di website Nomerin, sudah final dalam Rupiah. Base URL: https://api.nomerin.id
Alur singkat
- 1. Buat API key di dashboard Nomerin › API Reseller.
- 2. Ambil layanan, negara, lalu daftar harga.
- 3. Buat order dengan harga pilihan dan header
Idempotency-Key. - 4. Terima webhook saat nomor dan OTP keluar, atau polling status order.
- 5. Tidak dapat OTP? Batalkan setelah 2 menit untuk refund. Order kedaluwarsa direfund otomatis.
Contoh curl
curl -X POST https://api.nomerin.id/v1/orders \
-H "Authorization: Bearer nmr_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2026-0001" \
-d '{"service_id": 3, "country_id": 7, "price": 4500}'
Autentikasi
Setiap request membawa API key di header Authorization: Bearer nmr_.... Key dibuat dan dinonaktifkan dari dashboard, hanya ditampilkan satu kali saat dibuat. Simpan di server Anda, jangan di aplikasi klien atau browser. Satu akun boleh punya beberapa key, misalnya satu per aplikasi, masing-masing dengan webhook sendiri.
Format respons
Semua respons JSON memakai envelope yang sama. Cabangkan logika pada error.code yang stabil, bukan pada message yang bisa berubah. meta dan details hanya ada bila relevan.
{
"success": true,
"data": { ... },
"meta": { "has_more": false }
}
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Saldo tidak mencukupi untuk order ini.",
"details": { "balance": 1200, "price": 4500 }
}
}
Rate limit
Dihitung per API key per menit. Setiap respons membawa header X-RateLimit-Limit, X-RateLimit-Remaining, dan X-RateLimit-Reset (detik). Saat terlampaui Anda menerima HTTP 429 RATE_LIMIT_EXCEEDED dengan header Retry-After.
120/menit
Katalog, saldo, cek order
30/menit
Buat order
30/menit
Batalkan order
Selain itu maksimal 10 order aktif (processing atau waiting_otp) per API key pada saat yang sama. Lewat dari itu buat order ditolak TOO_MANY_ACTIVE_ORDERS sampai ada order yang selesai atau dibatalkan.
Idempotency
Kirim header Idempotency-Key (string unik dari sisi Anda, maksimal 64 karakter) saat membuat order. Jika koneksi putus dan Anda mengulang request yang sama, Nomerin mengembalikan order yang sama (HTTP 200 dan meta.idempotent_replay: true) alih-alih membuat order baru. Key disimpan 24 jam. Key yang sama dengan body berbeda ditolak IDEMPOTENCY_KEY_REUSED.
Siklus order
Saldo dipotong saat order dibuat dan dikembalikan penuh otomatis bila order berakhir refunded atau failed. Satu order untuk satu nomor.
processingOrder diterima dan sedang dicarikan nomor. Biasanya selesai dalam beberapa detik, paling lama kurang lebih 80 detik.
waiting_otpNomor sudah keluar (field number terisi). Pakai nomornya lalu tunggu OTP. Boleh dibatalkan setelah cancel_available_at.
successOTP masuk (otp_code terisi). Nomor masih bisa menerima OTP tambahan sampai expires_at, setiap OTP baru memicu webhook order.otp.
refundedTidak ada OTP sampai expires_at atau order dibatalkan. Saldo sudah dikembalikan penuh.
failedNomor tidak didapat setelah semua percobaan. Saldo sudah dikembalikan penuh.
Objek order
idintegerID order di Nomerin. Pakai ini untuk cek status dan cancel.
external_idstring|nullID dari sistem Anda yang dikirim saat membuat order (maksimal 64 karakter).
statusstringprocessing, waiting_otp, success, refunded, atau failed. Lihat bagian Siklus order.
serviceobjectLayanan: id, code, name.
countryobjectNegara: id, code (kode telepon negara, misalnya 62), name.
priceintegerHarga yang dipotong dari saldo (IDR). Sudah final, tidak ada biaya lain.
numberstring|nullNomor telepon format internasional (+62...). Terisi saat status waiting_otp.
otp_codestring|nullKode OTP terakhir yang masuk.
otp_codesarraySemua kode OTP yang masuk ke nomor ini, urut dari yang paling lama.
otp_received_atstring|nullWaktu OTP terakhir masuk (ISO 8601 dengan zona waktu).
created_atstringWaktu order dibuat.
expires_atstring|nullBatas nomor menerima OTP. Setelah ini order tanpa OTP otomatis refunded.
cancel_available_atstring|nullWaktu paling awal order boleh dibatalkan (2 menit setelah nomor keluar).
refunded_atstring|nullWaktu saldo dikembalikan (status refunded atau failed).
failure_reasonstring|nullNUMBER_UNAVAILABLE bila status failed: stok nomor habis di harga itu.
{
"id": 184212,
"external_id": "inv-2026-0001",
"status": "success",
"service": {
"id": 3,
"code": "whatsapp",
"name": "WhatsApp"
},
"country": {
"id": 7,
"code": "62",
"name": "Indonesia"
},
"price": 4500,
"number": "+628123456789",
"otp_code": "482913",
"otp_codes": [
"482913"
],
"otp_received_at": "2026-10-14T20:31:05+07:00",
"created_at": "2026-10-14T20:29:40+07:00",
"expires_at": "2026-10-14T20:49:40+07:00",
"cancel_available_at": null,
"refunded_at": null,
"failure_reason": null
}
/v1/balance
120 req/menit
Cek saldo
Saldo akun Nomerin pemilik API key, dalam IDR. Saldo diisi lewat deposit di dashboard Nomerin.
Contoh respons 200
{
"success": true,
"data": {
"balance": 250000,
"currency": "IDR"
}
}
/v1/services
120 req/menit
Daftar layanan
Semua layanan (WhatsApp, Telegram, dan lainnya) yang saat ini punya stok. countries adalah jumlah negara yang tersedia. Daftar ini berubah mengikuti stok, ambil ulang secara berkala, cukup tiap beberapa menit.
Contoh respons 200
{
"success": true,
"data": [
{
"id": 3,
"code": "whatsapp",
"name": "WhatsApp",
"countries": 42
},
{
"id": 8,
"code": "telegram",
"name": "Telegram",
"countries": 57
}
]
}
/v1/countries
120 req/menit
Daftar negara untuk satu layanan
Negara yang tersedia untuk service_id beserta harga termurah dan termahal (IDR) dan jumlah pilihan harga. low_stock true berarti semua pilihan harga stoknya tipis.
Query parameter
service_idinteger · wajibID layanan dari /v1/services.
Contoh respons 200
{
"success": true,
"data": [
{
"id": 7,
"code": "62",
"name": "Indonesia",
"min_price": 4500,
"max_price": 12000,
"offers": 6,
"low_stock": false
},
{
"id": 12,
"code": "MY",
"name": "Malaysia",
"min_price": 6000,
"max_price": 9500,
"offers": 3,
"low_stock": true
}
]
}
/v1/offers
120 req/menit
Daftar harga untuk layanan + negara
Semua pilihan harga untuk pasangan layanan dan negara, urut dari termurah. Nilai price inilah yang dikirim saat membuat order. Harga lebih tinggi umumnya nomor dengan kualitas atau tingkat keberhasilan lebih baik. label bisa null. low_stock true berarti stok tipis, bisa habis sebelum order diproses.
Query parameter
service_idinteger · wajibID layanan.
country_idinteger · wajibID negara dari /v1/countries.
Contoh respons 200
{
"success": true,
"data": {
"service": {
"id": 3,
"code": "whatsapp",
"name": "WhatsApp"
},
"country": {
"id": 7,
"code": "62",
"name": "Indonesia"
},
"offers": [
{
"price": 4500,
"label": "Hemat",
"low_stock": false
},
{
"price": 6000,
"label": null,
"low_stock": false
},
{
"price": 8500,
"label": "Premium",
"low_stock": true
}
]
}
}
/v1/orders
30 req/menit
Buat order nomor
Memesan satu nomor. price harus salah satu nilai dari /v1/offers; kalau harga itu sudah tidak ada, respons 409 OFFER_UNAVAILABLE dengan daftar harga terbaru di details.offers. Saldo dipotong saat order diterima dan dikembalikan otomatis bila order berakhir refunded atau failed. Order dikembalikan dengan status processing; nomor keluar beberapa detik kemudian (webhook order.number atau cek GET /v1/orders/{id}). Kirim header Idempotency-Key (string unik maksimal 64 karakter) supaya request yang diulang karena timeout tidak membuat order ganda: ulangan dengan body sama mengembalikan order yang sama dengan HTTP 200 dan meta.idempotent_replay true.
Header
Idempotency-KeystringKunci unik per order dari sisi Anda, disimpan 24 jam.
Body JSON
service_idinteger · wajibID layanan.
country_idinteger · wajibID negara.
priceinteger · wajibHarga pilihan dari /v1/offers (IDR).
external_idstringReferensi dari sistem Anda, maksimal 64 karakter. Ikut dikirim di webhook.
Contoh body
{
"service_id": 3,
"country_id": 7,
"price": 4500,
"external_id": "inv-2026-0001"
}
Contoh respons 201
{
"success": true,
"data": {
"id": 184212,
"external_id": "inv-2026-0001",
"status": "processing",
"service": {
"id": 3,
"code": "whatsapp",
"name": "WhatsApp"
},
"country": {
"id": 7,
"code": "62",
"name": "Indonesia"
},
"price": 4500,
"number": null,
"otp_code": null,
"otp_codes": [],
"otp_received_at": null,
"created_at": "2026-10-14T20:29:40+07:00",
"expires_at": null,
"cancel_available_at": "2026-10-14T20:31:40+07:00",
"refunded_at": null,
"failure_reason": null
}
}
Error khusus endpoint ini
- 422
VALIDATION_ERRORBody atau query tidak valid. details berisi field yang salah. - 404
SERVICE_NOT_FOUNDservice_id tidak ada atau layanan sedang tidak tersedia. - 409
OFFER_UNAVAILABLEHarga yang dikirim sudah tidak ada. details.offers berisi daftar harga saat ini, pilih lalu kirim ulang. - 402
INSUFFICIENT_BALANCESaldo tidak cukup. details berisi balance dan price. - 429
TOO_MANY_ACTIVE_ORDERSSudah ada 10 order aktif (processing atau waiting_otp) di API key ini. - 429
ORDER_BLOCKEDTerlalu banyak order dibatalkan atau kedaluwarsa tanpa OTP. details.retry_after dalam detik. - 409
IDEMPOTENCY_KEY_REUSEDIdempotency-Key sama dipakai dengan body berbeda. - 409
REQUEST_IN_PROGRESSRequest dengan Idempotency-Key yang sama masih diproses. - 503
PURCHASE_DISABLEDPembelian nomor dinonaktifkan sementara. Coba lagi sesuai Retry-After.
/v1/orders/{order_id}
120 req/menit
Detail dan status order
Status terkini satu order termasuk nomor dan semua OTP yang masuk. Kalau tidak memakai webhook, polling endpoint ini tiap 3 sampai 5 detik sudah cukup.
Path parameter
order_idinteger · wajibID order.
Contoh respons 200
{
"success": true,
"data": {
"id": 184212,
"external_id": "inv-2026-0001",
"status": "success",
"service": {
"id": 3,
"code": "whatsapp",
"name": "WhatsApp"
},
"country": {
"id": 7,
"code": "62",
"name": "Indonesia"
},
"price": 4500,
"number": "+628123456789",
"otp_code": "482913",
"otp_codes": [
"482913"
],
"otp_received_at": "2026-10-14T20:31:05+07:00",
"created_at": "2026-10-14T20:29:40+07:00",
"expires_at": "2026-10-14T20:49:40+07:00",
"cancel_available_at": null,
"refunded_at": null,
"failure_reason": null
}
}
Error khusus endpoint ini
- 404
ORDER_NOT_FOUNDOrder tidak ada atau bukan milik API key ini.
/v1/orders
120 req/menit
Daftar order
Order yang dibuat lewat API key ini, terbaru lebih dulu. Untuk halaman berikutnya kirim before_id = meta.next_before_id.
Query parameter
statusstringFilter status: processing, waiting_otp, success, refunded, failed.
external_idstringCari order berdasarkan external_id Anda.
before_idintegerHanya order dengan id lebih kecil dari ini (paging).
limitintegerJumlah per halaman, 1 sampai 100, default 20.
Contoh respons 200
{
"success": true,
"data": [
{
"id": 184212,
"external_id": "inv-2026-0001",
"status": "success",
"service": {
"id": 3,
"code": "whatsapp",
"name": "WhatsApp"
},
"country": {
"id": 7,
"code": "62",
"name": "Indonesia"
},
"price": 4500,
"number": "+628123456789",
"otp_code": "482913",
"otp_codes": [
"482913"
],
"otp_received_at": "2026-10-14T20:31:05+07:00",
"created_at": "2026-10-14T20:29:40+07:00",
"expires_at": "2026-10-14T20:49:40+07:00",
"cancel_available_at": null,
"refunded_at": null,
"failure_reason": null
}
],
"meta": {
"has_more": true,
"next_before_id": 184212
}
}
Error khusus endpoint ini
- 422
VALIDATION_ERRORBody atau query tidak valid. details berisi field yang salah.
/v1/orders/{order_id}/cancel
30 req/menit
Batalkan order
Membatalkan order yang masih waiting_otp dan belum menerima OTP, paling cepat 2 menit setelah nomor keluar (lihat cancel_available_at). Saldo kembali penuh dan status menjadi refunded. Order yang sudah refunded atau failed mengembalikan 200 dengan data order apa adanya. Terlalu sering membatalkan atau membiarkan order kedaluwarsa tanpa OTP akan memblokir order baru sementara (ORDER_BLOCKED).
Path parameter
order_idinteger · wajibID order.
Contoh respons 200
{
"success": true,
"data": {
"id": 184212,
"external_id": "inv-2026-0001",
"status": "refunded",
"service": {
"id": 3,
"code": "whatsapp",
"name": "WhatsApp"
},
"country": {
"id": 7,
"code": "62",
"name": "Indonesia"
},
"price": 4500,
"number": "+628123456789",
"otp_code": null,
"otp_codes": [],
"otp_received_at": null,
"created_at": "2026-10-14T20:29:40+07:00",
"expires_at": "2026-10-14T20:49:40+07:00",
"cancel_available_at": null,
"refunded_at": "2026-10-14T20:35:02+07:00",
"failure_reason": null
}
}
Error khusus endpoint ini
- 404
ORDER_NOT_FOUNDOrder tidak ada atau bukan milik API key ini. - 409
CANCEL_NOT_ALLOWEDOrder bukan waiting_otp, sudah selesai, atau sudah berubah. - 409
CANCEL_TOO_EARLYBelum lewat cancel_available_at (2 menit setelah nomor keluar). - 409
OTP_ALREADY_RECEIVEDOTP sudah masuk, order tidak bisa dibatalkan. Cek ulang status order. - 503
CANCEL_FAILEDPembatalan gagal sementara, coba lagi beberapa detik kemudian.
Kode error
Semua endpoint bisa mengembalikan UNAUTHORIZED, KEY_REVOKED, RATE_LIMIT_EXCEEDED, dan INTERNAL_ERROR.
UNAUTHORIZEDAPI key tidak dikirim, formatnya salah, atau tidak dikenal.
KEY_REVOKEDAPI key sudah dinonaktifkan dari dashboard.
INSUFFICIENT_BALANCESaldo tidak cukup. details berisi balance dan price.
NOT_FOUNDEndpoint tidak ada.
SERVICE_NOT_FOUNDservice_id tidak ada atau layanan sedang tidak tersedia.
COUNTRY_NOT_FOUNDcountry_id tidak tersedia untuk layanan itu.
ORDER_NOT_FOUNDOrder tidak ada atau bukan milik API key ini.
OFFER_UNAVAILABLEHarga yang dikirim sudah tidak ada. details.offers berisi daftar harga saat ini, pilih lalu kirim ulang.
IDEMPOTENCY_KEY_REUSEDIdempotency-Key sama dipakai dengan body berbeda.
REQUEST_IN_PROGRESSRequest dengan Idempotency-Key yang sama masih diproses.
CANCEL_NOT_ALLOWEDOrder bukan waiting_otp, sudah selesai, atau sudah berubah.
CANCEL_TOO_EARLYBelum lewat cancel_available_at (2 menit setelah nomor keluar).
OTP_ALREADY_RECEIVEDOTP sudah masuk, order tidak bisa dibatalkan. Cek ulang status order.
VALIDATION_ERRORBody atau query tidak valid. details berisi field yang salah.
RATE_LIMIT_EXCEEDEDMelebihi batas request per menit. Hormati header Retry-After.
TOO_MANY_ACTIVE_ORDERSSudah ada 10 order aktif (processing atau waiting_otp) di API key ini.
ORDER_BLOCKEDTerlalu banyak order dibatalkan atau kedaluwarsa tanpa OTP. details.retry_after dalam detik.
PURCHASE_DISABLEDPembelian nomor dinonaktifkan sementara. Coba lagi sesuai Retry-After.
CANCEL_FAILEDPembatalan gagal sementara, coba lagi beberapa detik kemudian.
INTERNAL_ERRORKesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.
Webhook
Atur URL webhook (https) per API key di dashboard. Nomerin mengirim POST JSON setiap status order berubah, jadi Anda tidak perlu polling.
order.numberNomor sudah keluar, status waiting_otp.
order.otpOTP masuk, status success. Dikirim lagi setiap ada OTP baru di nomor yang sama.
order.refundedOrder dibatalkan atau kedaluwarsa tanpa OTP, saldo kembali.
order.failedNomor tidak didapat, saldo kembali.
webhook.testTes dari tombol di dashboard. Field order bernilai null.
Body yang dikirim
{
"event": "order.otp",
"created_at": "2026-10-14T20:31:05+07:00",
"order": {
"id": 184212,
"external_id": "inv-2026-0001",
"status": "success",
"service": {
"id": 3,
"code": "whatsapp",
"name": "WhatsApp"
},
"country": {
"id": 7,
"code": "62",
"name": "Indonesia"
},
"price": 4500,
"number": "+628123456789",
"otp_code": "482913",
"otp_codes": [
"482913"
],
"otp_received_at": "2026-10-14T20:31:05+07:00",
"created_at": "2026-10-14T20:29:40+07:00",
"expires_at": "2026-10-14T20:49:40+07:00",
"cancel_available_at": null,
"refunded_at": null,
"failure_reason": null
}
}
Header
X-Nomerin-Event
Nama event, misalnya order.otp.
X-Nomerin-Delivery
ID pengiriman, unik per percobaan kirim. Pakai untuk mengabaikan duplikat.
X-Nomerin-Timestamp
Unix timestamp (detik) saat dikirim.
X-Nomerin-Signature
t=<timestamp>,v1=<hex HMAC-SHA256>. Dihitung dari secret webhook atas string "<timestamp>.<body mentah>".
Aturan percobaan ulang
Hanya HTTP 200 yang dianggap sukses. Balasan lain atau tidak ada respons dalam 10 detik dikirim ulang maksimal 5 kali total dengan jeda 5 detik, 15 detik, 40 detik, 90 detik. Balas 200 secepatnya lalu proses isinya secara asinkron. Pengiriman bisa duplikat atau tidak urut, jadi dedupe dengan order.id + status + otp_code.
Verifikasi tanda tangan (Python)
import hmac, hashlib, time
def verify(secret, header, raw_body, tolerance=300):
parts = dict(item.split("=", 1) for item in header.split(","))
timestamp, received = parts["t"], parts["v1"]
if abs(time.time() - int(timestamp)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received)
Verifikasi tanda tangan (Node.js)
const crypto = require("crypto");
function verify(secret, header, rawBody, tolerance = 300) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > tolerance) return false;
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
Untuk AI dan tools
Dokumentasi ini juga tersedia dalam format yang mudah dibaca mesin. Tempel URL di bawah ke asisten AI atau generator SDK Anda.
- https://api.nomerin.id/llms.txt
Ringkasan padat seluruh API, cocok untuk konteks AI.
- https://api.nomerin.id/docs.md
Dokumentasi lengkap dalam Markdown (alias
/llms-full.txt). - https://api.nomerin.id/openapi.json
Spesifikasi OpenAPI 3 untuk generator client dan Postman.
- https://api.nomerin.id/openapi.yaml
Spesifikasi yang sama dalam YAML.