Nomerin Logo
Nomerin Nomor Virtual

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. 1. Buat API key di dashboard Nomerin › API Reseller.
  2. 2. Ambil layanan, negara, lalu daftar harga.
  3. 3. Buat order dengan harga pilihan dan header Idempotency-Key.
  4. 4. Terima webhook saat nomor dan OTP keluar, atau polling status order.
  5. 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.

processing

Order diterima dan sedang dicarikan nomor. Biasanya selesai dalam beberapa detik, paling lama kurang lebih 80 detik.

waiting_otp

Nomor sudah keluar (field number terisi). Pakai nomornya lalu tunggu OTP. Boleh dibatalkan setelah cancel_available_at.

success

OTP masuk (otp_code terisi). Nomor masih bisa menerima OTP tambahan sampai expires_at, setiap OTP baru memicu webhook order.otp.

refunded

Tidak ada OTP sampai expires_at atau order dibatalkan. Saldo sudah dikembalikan penuh.

failed

Nomor tidak didapat setelah semua percobaan. Saldo sudah dikembalikan penuh.

Objek order

idinteger

ID order di Nomerin. Pakai ini untuk cek status dan cancel.

external_idstring|null

ID dari sistem Anda yang dikirim saat membuat order (maksimal 64 karakter).

statusstring

processing, waiting_otp, success, refunded, atau failed. Lihat bagian Siklus order.

serviceobject

Layanan: id, code, name.

countryobject

Negara: id, code (kode telepon negara, misalnya 62), name.

priceinteger

Harga yang dipotong dari saldo (IDR). Sudah final, tidak ada biaya lain.

numberstring|null

Nomor telepon format internasional (+62...). Terisi saat status waiting_otp.

otp_codestring|null

Kode OTP terakhir yang masuk.

otp_codesarray

Semua kode OTP yang masuk ke nomor ini, urut dari yang paling lama.

otp_received_atstring|null

Waktu OTP terakhir masuk (ISO 8601 dengan zona waktu).

created_atstring

Waktu order dibuat.

expires_atstring|null

Batas nomor menerima OTP. Setelah ini order tanpa OTP otomatis refunded.

cancel_available_atstring|null

Waktu paling awal order boleh dibatalkan (2 menit setelah nomor keluar).

refunded_atstring|null

Waktu saldo dikembalikan (status refunded atau failed).

failure_reasonstring|null

NUMBER_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
}
GET /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"
  }
}
GET /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
    }
  ]
}
GET /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 · wajib

ID 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
    }
  ]
}
GET /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 · wajib

ID layanan.

country_idinteger · wajib

ID 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
      }
    ]
  }
}
POST /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-Keystring

Kunci unik per order dari sisi Anda, disimpan 24 jam.

Body JSON

service_idinteger · wajib

ID layanan.

country_idinteger · wajib

ID negara.

priceinteger · wajib

Harga pilihan dari /v1/offers (IDR).

external_idstring

Referensi 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

  • 422VALIDATION_ERROR Body atau query tidak valid. details berisi field yang salah.
  • 404SERVICE_NOT_FOUND service_id tidak ada atau layanan sedang tidak tersedia.
  • 409OFFER_UNAVAILABLE Harga yang dikirim sudah tidak ada. details.offers berisi daftar harga saat ini, pilih lalu kirim ulang.
  • 402INSUFFICIENT_BALANCE Saldo tidak cukup. details berisi balance dan price.
  • 429TOO_MANY_ACTIVE_ORDERS Sudah ada 10 order aktif (processing atau waiting_otp) di API key ini.
  • 429ORDER_BLOCKED Terlalu banyak order dibatalkan atau kedaluwarsa tanpa OTP. details.retry_after dalam detik.
  • 409IDEMPOTENCY_KEY_REUSED Idempotency-Key sama dipakai dengan body berbeda.
  • 409REQUEST_IN_PROGRESS Request dengan Idempotency-Key yang sama masih diproses.
  • 503PURCHASE_DISABLED Pembelian nomor dinonaktifkan sementara. Coba lagi sesuai Retry-After.
GET /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 · wajib

ID 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

  • 404ORDER_NOT_FOUND Order tidak ada atau bukan milik API key ini.
GET /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

statusstring

Filter status: processing, waiting_otp, success, refunded, failed.

external_idstring

Cari order berdasarkan external_id Anda.

before_idinteger

Hanya order dengan id lebih kecil dari ini (paging).

limitinteger

Jumlah 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

  • 422VALIDATION_ERROR Body atau query tidak valid. details berisi field yang salah.
POST /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 · wajib

ID 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

  • 404ORDER_NOT_FOUND Order tidak ada atau bukan milik API key ini.
  • 409CANCEL_NOT_ALLOWED Order bukan waiting_otp, sudah selesai, atau sudah berubah.
  • 409CANCEL_TOO_EARLY Belum lewat cancel_available_at (2 menit setelah nomor keluar).
  • 409OTP_ALREADY_RECEIVED OTP sudah masuk, order tidak bisa dibatalkan. Cek ulang status order.
  • 503CANCEL_FAILED Pembatalan gagal sementara, coba lagi beberapa detik kemudian.

Kode error

Semua endpoint bisa mengembalikan UNAUTHORIZED, KEY_REVOKED, RATE_LIMIT_EXCEEDED, dan INTERNAL_ERROR.

401UNAUTHORIZED

API key tidak dikirim, formatnya salah, atau tidak dikenal.

401KEY_REVOKED

API key sudah dinonaktifkan dari dashboard.

402INSUFFICIENT_BALANCE

Saldo tidak cukup. details berisi balance dan price.

404NOT_FOUND

Endpoint tidak ada.

404SERVICE_NOT_FOUND

service_id tidak ada atau layanan sedang tidak tersedia.

404COUNTRY_NOT_FOUND

country_id tidak tersedia untuk layanan itu.

404ORDER_NOT_FOUND

Order tidak ada atau bukan milik API key ini.

409OFFER_UNAVAILABLE

Harga yang dikirim sudah tidak ada. details.offers berisi daftar harga saat ini, pilih lalu kirim ulang.

409IDEMPOTENCY_KEY_REUSED

Idempotency-Key sama dipakai dengan body berbeda.

409REQUEST_IN_PROGRESS

Request dengan Idempotency-Key yang sama masih diproses.

409CANCEL_NOT_ALLOWED

Order bukan waiting_otp, sudah selesai, atau sudah berubah.

409CANCEL_TOO_EARLY

Belum lewat cancel_available_at (2 menit setelah nomor keluar).

409OTP_ALREADY_RECEIVED

OTP sudah masuk, order tidak bisa dibatalkan. Cek ulang status order.

422VALIDATION_ERROR

Body atau query tidak valid. details berisi field yang salah.

429RATE_LIMIT_EXCEEDED

Melebihi batas request per menit. Hormati header Retry-After.

429TOO_MANY_ACTIVE_ORDERS

Sudah ada 10 order aktif (processing atau waiting_otp) di API key ini.

429ORDER_BLOCKED

Terlalu banyak order dibatalkan atau kedaluwarsa tanpa OTP. details.retry_after dalam detik.

503PURCHASE_DISABLED

Pembelian nomor dinonaktifkan sementara. Coba lagi sesuai Retry-After.

503CANCEL_FAILED

Pembatalan gagal sementara, coba lagi beberapa detik kemudian.

500INTERNAL_ERROR

Kesalahan 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.number

Nomor sudah keluar, status waiting_otp.

order.otp

OTP masuk, status success. Dikirim lagi setiap ada OTP baru di nomor yang sama.

order.refunded

Order dibatalkan atau kedaluwarsa tanpa OTP, saldo kembali.

order.failed

Nomor tidak didapat, saldo kembali.

webhook.test

Tes 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.