Menu utama

Developer · API

Public API v1 · Integrator

Hubungkan website atau bot ke katalog gabungan server 1–5, saldo, pembelian nomor, dan OTP menggunakan API key akun sendiri.

Unduh OpenAPI integrator

01 · Buat key untuk backend integrasi

Login ke ping, buka Pengaturan akun → Developer API. Isi nama integrasi dan pilih izin yang diperlukan. Untuk alur penuh, aktifkan catalog:read, wallet:read, orders:read, dan orders:write. account:read opsional untuk profil. Nilai lengkap key hanya ditampilkan satu kali; pembuatan dan pencabutan tetap memerlukan sesi browser.

01 · Buat key untuk backend integrasi
ScopeAkses
account:readMembaca profil akun pemilik key.
wallet:readMembaca saldo, alokasi, dan saldo tersedia.
catalog:readMembaca layanan, negara, dan quote gabungan.
orders:readMembaca riwayat, nomor, dan SMS akun sendiri.
orders:writeMembeli memakai saldo akun, membatalkan, menyelesaikan, dan resend.
orders:write menggunakan saldo nyata

Key dengan izin ini dapat membeli nomor dan melakukan aksi atas seluruh pesanan akun pemiliknya, termasuk yang dibuat dari website atau key lain. Hanya berikan ke integrasi tepercaya. Isi saldo lewat aplikasi ping!; API key tidak dapat membuat top-up, mengubah saldo langsung, mengelola key, atau mengakses admin.

  • Maksimal lima key aktif per akun, berlaku 90 hari, pembuatan maksimal sepuluh kali per jam.
  • Izin key lama tidak bertambah otomatis. Buat key baru untuk menambah izin, pindahkan integrasi, lalu cabut key lama.
  • Simpan key hanya di secret manager atau environment backend integrator. Jangan commit, masukkan frontend, kirim sebagai query, atau mencetak Authorization/OTP ke log.
  • Pencabutan, kedaluwarsa, penghapusan akun, atau penangguhan menghentikan akses request berikutnya.

02 · Kontrak request dan endpoint

Base URL produksi: https://pingotp.com/api/v1. Kirim Authorization: Bearer <API_KEY> pada setiap request dan Content-Type: application/json untuk POST. Tidak perlu login cookie di backend integrator; cookie tidak menjadi fallback. Respons memakai { ok: true, data } atau { ok: false, error: { code, message } }. Tidak ada CORS publik untuk pemanggilan key dari browser lintas origin.

02 · Kontrak request dan endpoint
Method & pathScopeFungsi
GET /api/v1/meaccount:readIdentitas akun
GET /api/v1/walletwallet:readSaldo dompet
GET /api/v1/catalog/servicescatalog:readLayanan gabungan
GET /api/v1/catalog/countriescatalog:readNegara gabungan
GET /api/v1/catalog/quotescatalog:readQuote gabungan
GET /api/v1/ordersorders:readRiwayat pesanan
POST /api/v1/ordersorders:writeBeli nomor
GET /api/v1/orders/activeorders:readPesanan aktif
GET /api/v1/orders/{id}orders:readStatus & OTP
POST /api/v1/orders/{id}/cancelorders:writeBatalkan pesanan
POST /api/v1/orders/{id}/completeorders:writeSelesaikan pesanan
POST /api/v1/orders/{id}/resendorders:writeMinta SMS berikutnya
Cek saldo · backend
curl --fail-with-body \
  -H "Authorization: Bearer $PING_API_KEY" \
  "https://pingotp.com/api/v1/wallet"

Unduh OpenAPI integrator menggunakan tombol di atas atau /developers/integrator-openapi.json. Spesifikasi ini memakai Bearer; /developers/openapi.json tetap khusus API internal berbasis sesi. Semua contoh dokumentasi adalah ilustrasi dan tidak menjalankan transaksi.

03 · Satu katalog, tanpa memilih server

  1. Langkah 1

    Ambil layanan

    GET /catalog/services menggabungkan layanan dari server 1–5 yang aktif dan dikonfigurasi tanpa duplikat. Gunakan id dari respons, bukan nama atau kode provider.

  2. Langkah 2

    Ambil negara untuk layanan

    GET /catalog/countries?serviceId=... menampilkan negara, harga mulai sesuai akun pemilik key, serta ketersediaan indikatif. Tanpa serviceId, respons hanya katalog negara aktif.

  3. Langkah 3

    Ambil quote

    GET /catalog/quotes?serviceId=...&countryId=... memberikan opsi harga terurut dan selection sumber yang sudah ditentukan server. provider boleh dihilangkan atau diisi all; provider lain ditolak pada API v1 ini.

03 · Satu katalog, tanpa memilih server
FieldPenggunaan
services[].logo_urlURL absolut artwork layanan yang sama dengan website, atau null jika belum ada. icon tetap kunci ikon, bukan URL.
countries[].flag_urlURL gambar bendera berdasarkan ISO2, atau null jika negara tidak dikenali. flag dan iso2 tetap disertakan.
options[].selectionTarget awal pembelian yang tepat: service.id dan country.id. Salin keduanya dari opsi yang sama; tidak perlu menentukan provider/server sendiri.
options[].price_idr / idHarga yang disetujui / priceOptionId untuk pembelian. Jangan mengarang ID atau menaikkan batas harga saat retry.
expires_at / stock_countMasa berlaku quote / indikasi stok, bukan reservasi nomor atau jaminan OTP. Harga diperiksa lagi saat membeli.
Baca katalog & quote · Node.js di backend
const origin = "https://pingotp.com";
const apiKey = process.env.PING_API_KEY;
if (!apiKey) throw new Error("API key belum dikonfigurasi");

async function ping(path, init = {}) {
  const response = await fetch(`${origin}/api/v1${path}`, {
    ...init,
    cache: "no-store",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
  });
  const body = await response.json();
  if (!response.ok || !body.ok) {
    throw Object.assign(new Error(body.error?.message ?? "Request gagal"), {
      code: body.error?.code,
      retryAfter: response.headers.get("retry-after"),
    });
  }
  return body.data;
}

const services = await ping("/catalog/services");
// Gunakan id layanan yang dipilih pelanggan dari respons services.
async function loadCountries(serviceId) {
  return ping(`/catalog/countries?${new URLSearchParams({ serviceId })}`);
}
async function loadQuote(serviceId, countryId) {
  return ping(`/catalog/quotes?${new URLSearchParams({ serviceId, countryId })}`);
}

04 · Pembelian yang idempotent

Simpan intent sebelum mengirim

Simpan satu UUID clientRequestId dan seluruh payload di database backend integrator sebelum POST. UUID harus tetap untuk satu niat beli, termasuk restart, timeout, atau respons hilang. Header Idempotency-Key bukan pengganti field clientRequestId. API memproses satu nomor per request.

Hanya panggil setelah harga disetujui
async function buyConfirmed(option, savedClientRequestId) {
  return ping("/orders", {
    method: "POST",
    body: JSON.stringify({
      serviceId: option.selection.service.id,
      countryId: option.selection.country.id,
      priceOptionId: option.id,
      expectedPriceIdr: option.price_idr,
      clientRequestId: savedClientRequestId,
    }),
  });
}

// Jangan panggil otomatis ketika memuat halaman atau mengambil quote.
// Simpan payload persis dan UUID yang sama untuk pemulihan respons hilang.
  • Gunakan ID di options[].selection, bukan serviceId/countryId representatif pada root quote. Jangan kirim provider; server menentukannya dari sumber layanan.
  • expectedPriceIdr wajib sama dengan harga opsi. maxPriceIdr opsional hanya untuk batas fallback yang disetujui sebelumnya dan tidak boleh lebih kecil; tanpa itu harga fallback tidak melebihi harga awal.
  • Alur memakai hold, batas pesanan, validasi harga, diskon rank, fallback aman setelah penolakan pasti, dan settlement yang sama dengan aplikasi. Hasil ambigu tidak dilanjutkan dengan pembelian baru.
  • HTTP 200 mengembalikan hasil order, bukan jaminan nomor berhasil. Periksa status, isPreparing, message, dan id hasil. Fallback dapat menghasilkan id order berbeda; simpan id yang dikembalikan.
  • Replay payload yang sama memulihkan hasil awal; perubahan pilihan, harga, atau ceiling memakai UUID yang sudah terpakai menghasilkan IDEMPOTENCY_CONFLICT. Jangan ganti UUID karena timeout atau HTTP 5xx.

Riwayat GET /orders menerima limit 1–100 (default 20), offset 0–1000000 (default 0), status all/waiting/completed/cancelled/failed, dan search maksimal 128 karakter. cancelled mencakup expired. Respons berupa { orders, counts, pagination }; pencarian memakai ID order, nomor, layanan, atau negara, bukan clientRequestId. Untuk respons pembelian hilang, replay POST dengan payload persis yang tersimpan.

05 · Pantau OTP dan selesaikan

Baca status · tidak membeli nomor lagi
const order = await ping(`/orders/${encodeURIComponent(savedOrderId)}`);

// Poll paling cepat setiap 5 detik; hentikan saat terminal.
// Jangan log code atau smsText ke log produksi.
if (order.canComplete) {
  // Tampilkan SMS ke pelanggan yang berhak.
  // Kirim POST /orders/{id}/complete dengan body {} setelah selesai.
}
05 · Pantau OTP dan selesaikan
Field / aksiAturan
statuswaiting, completed, cancelled, expired, atau failed. waiting dapat sudah menerima SMS; periksa canComplete, code, dan smsText.
isPreparingNomor masih disiapkan atau hasil upstream diperiksa. Jangan membuat pembelian baru untuk menggantikannya.
POST /orders/{id}/cancel · {}Hanya sebelum SMS dan saat canCancel. Waktu izin ada di cancelAvailableAt. Refund/release menunggu konfirmasi provider.
POST /orders/{id}/complete · {}Saat canComplete. Settlement tidak membebankan biaya ulang ketika request diulang.
POST /orders/{id}/resend · { requestId }Saat canResend; satu UUID tetap untuk satu resend. Dukungan mengikuti provider. Tidak membeli nomor baru atau menambah biaya.
createdAt / expiresAtUnix milidetik pada order, berbeda dari fetched_at/expires_at ISO 8601 pada quote.

Tidak ada webhook keluar ke integrator. Gunakan polling detail dengan backoff; GET /orders dan GET /orders/active hanya membaca database. Pembatasan pesanan dan saldo mengikuti akun, bukan jumlah API key.

Rate limit, error, dan pemulihan

Batas per menit: 60 request per key, 120 per akun lintas key, dan 300 per IP tepercaya (600 untuk jaringan tanpa IP terpercaya yang teridentifikasi). Pembacaan penawaran negara dan quote juga berbagi batas katalog 20 per akun per menit serta 200 per IP dengan aplikasi. Batas bisnis pembelian tetap berlaku. Body POST maksimal 4096 byte; parameter asing, query berulang, userId, harga non-integer, dan provider pada pembelian ditolak.

Rate limit, error, dan pemulihan
HTTP / kodeTindakan
401 · UNAUTHENTICATEDKey salah, dicabut, kedaluwarsa, atau akun nonaktif. Hentikan request dan periksa key di UI.
403 · FORBIDDENScope belum diberikan. Izin key tidak bisa dinaikkan lewat request; buat key baru melalui UI.
404 · NOT_FOUNDPesanan tidak ada atau bukan milik pemilik key, termasuk jika pemilik key admin.
409 · PRICE_CHANGED / PRICE_OPTION_UNAVAILABLEAmbil quote baru dan minta persetujuan ulang untuk niat baru setelah hasil sebelumnya dipastikan.
409 · IDEMPOTENCY_CONFLICTPeriksa intent awal. Jangan mengubah payload atau UUID untuk menutupi konflik.
422 · INSUFFICIENT_BALANCEIsi saldo melalui aplikasi. API tidak menambah saldo sendiri.
429Patuhi Retry-After, antrekan polling, dan jangan merotasi key untuk melewati batas akun.
5xx / timeoutHasil mutasi dapat belum pasti. Pulihkan dengan UUID dan payload semula, bukan pesanan baru.

Gunakan error.code, bukan teks message. X-Request-ID tersedia untuk bantuan diagnosis tanpa mengirim API key, OTP, atau isi SMS. Seluruh respons akun private/no-store. API internal /api/orders, /api/payments, pengelolaan key, admin, dan webhook tetap memakai proteksi semula; Bearer hanya berlaku pada /api/v1.

ping! · Panduan yang tumbuh bersama produknya.