Dokumentasi API v2
Hubungkan toko, website, atau bot kamu ke JosJis Topup. REST API berbasis JSON dengan API key, pesanan idempoten, dan webhook bertanda tangan.
Mulai
- 1Masuk atau buat akun, lalu isi saldo. Semua pesanan API dibayar dari saldo akun.
- 2Pasang 2FA di Akun > Keamanan, lalu buka Akun > API, nyalakan Akses API dengan kode 2FA, dan salin API key. Selama akses API mati, semua request ditolak.
- 3Opsional: isi IP whitelist dan URL webhook di halaman yang sama.
- Base URL
- https://josjistopup.com/api/v2
- Format
- JSON (UTF-8). Body boleh JSON atau form.
- Waktu
- ISO 8601, zona Asia/Jakarta (
+07:00) - Mata uang
- Rupiah (IDR), angka bulat tanpa desimal
Autentikasi
Kirim API key di header setiap request. Jangan simpan API key di aplikasi yang bisa dibuka pengguna (HTML, aplikasi Android, dan sejenisnya); panggil API dari server kamu.
X-API-Key: API_KEY_KAMU- Alternatif:
Authorization: Bearer API_KEY_KAMU. Untuk klien yang tidak bisa mengirim header, parameterapi_keydi body (form atau JSON) juga diterima. API key di query string (URL) ditolak. - Batas request: 120 request per menit per akun. Sisa kuota ada di header
X-RateLimit-Remaining; bila habis, respons 429 dengan headerRetry-After. - IP whitelist: bila diisi, request dari IP lain ditolak (403
IP_NOT_ALLOWED).
Format respons
Semua respons memakai amplop yang sama. Pakai code untuk logika program; message untuk ditampilkan atau dicatat. Simpan request_id saat menghubungi admin.
{
"success": true,
"code": "OK",
"message": "Saldo berhasil diambil.",
"data": {
"balance": 250000
},
"request_id": "req_8f3a2c1d9b7e4a60"
}{
"success": false,
"code": "VALIDATION_ERROR",
"message": "Ada parameter yang belum benar.",
"errors": {
"user_id": "Wajib diisi."
},
"request_id": "req_8f3a2c1d9b7e4a60"
}Kode HTTP
- 200
- Berhasil
- 201
- Pesanan baru dibuat
- 400
- Parameter salah
- 401
- API key tidak ada atau salah
- 403
- Akun, akses API, atau IP ditolak
- 404
- Data tidak ditemukan
- 405
- Metode HTTP salah
- 409
- reference_id bentrok
- 422
- Tidak bisa diproses (saldo, produk)
- 429
- Terlalu banyak request
- 503
- Sedang perawatan
- 500
- Kesalahan server
Daftar endpoint
Saldo
Nama, level, dan sisa saldo akun.
https://josjistopup.com/api/v2/balancecurl https://josjistopup.com/api/v2/balance \
-H "X-API-Key: API_KEY_KAMU"{
"success": true,
"code": "OK",
"message": "Saldo berhasil diambil.",
"data": {
"name": "Budi",
"email": "[email protected]",
"level": "gold",
"balance": 250000,
"currency": "IDR"
},
"request_id": "req_8f3a2c1d9b7e4a60"
}Kategori
Game dan produk yang aktif, beserta kolom data akun yang harus dikirim saat order (fields).
https://josjistopup.com/api/v2/categoriestypestringOpsional- Saring per jenis:
game,voucher,pulsa,pln,live-app. searchstringOpsional- Cari berdasarkan nama, maksimal 60 karakter.
curl "https://josjistopup.com/api/v2/categories?type=game&search=mobile" \
-H "X-API-Key: API_KEY_KAMU"{
"success": true,
"code": "OK",
"message": "Daftar kategori berhasil diambil.",
"data": [
{
"id": 1,
"name": "Mobile Legends",
"slug": "mobile-legends",
"type": {
"slug": "game",
"name": "Game"
},
"publisher": "Moonton",
"image": "https://josjistopup.com/assets/img/game/mobile-legends.webp",
"fields": [
{
"key": "user_id",
"label": "User ID",
"type": "number",
"required": true
},
{
"key": "zone_id",
"label": "Server ID",
"type": "number",
"required": true
}
]
}
],
"meta": {
"total": 1
},
"request_id": "req_8f3a2c1d9b7e4a60"
}Setiap item di fields adalah parameter yang dikirim saat order: user_id selalu ada, zone_id hanya untuk produk yang memintanya (misalnya Server ID Mobile Legends atau server Genshin Impact). Untuk field bertipe select, isi dengan salah satu nilai di options.
Produk
Nominal yang bisa dipesan, dengan harga sesuai level akun kamu.
https://josjistopup.com/api/v2/productscategory_idintegerOpsional- Hanya produk dari kategori ini (lihat
/categories). typestringOpsional- Saring per jenis, misalnya
game. searchstringOpsional- Cari di nama produk atau kategori, contoh
86 diamond. pageintegerOpsional- Halaman, mulai dari 1. Bawaan 1.
per_pageintegerOpsional- Jumlah per halaman, 1 sampai 500. Bawaan 100.
curl "https://josjistopup.com/api/v2/products?category_id=1&per_page=50" \
-H "X-API-Key: API_KEY_KAMU"{
"success": true,
"code": "OK",
"message": "Daftar produk berhasil diambil.",
"data": [
{
"id": 12,
"name": "86 Diamond",
"category": {
"id": 1,
"name": "Mobile Legends",
"slug": "mobile-legends"
},
"price": 23800,
"status": "available"
},
{
"id": 13,
"name": "172 Diamond",
"category": {
"id": 1,
"name": "Mobile Legends",
"slug": "mobile-legends"
},
"price": 47500,
"status": "available"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 2,
"total_pages": 1,
"currency": "IDR",
"level": "gold"
},
"request_id": "req_8f3a2c1d9b7e4a60"
}Harga bisa berubah mengikuti penyedia. Ambil ulang daftar produk secara berkala (misalnya tiap 10 menit), jangan menyimpan harga terlalu lama.
Buat pesanan
Saldo langsung dipotong dan pesanan masuk antrean proses. Hasil akhirnya dikirim lewat webhook, atau cek dengan /status.
https://josjistopup.com/api/v2/orderproduct_idintegerWajib- ID produk dari
/products. user_idstringWajib- ID akun game tujuan. Untuk pulsa dan PLN, isi nomor HP atau nomor meter. Maksimal 64 karakter.
zone_idstringOpsional- Zone ID atau server. Wajib untuk produk yang memintanya (lihat
fieldsdi/categories). Nama lain:server_id. reference_idstringWajib- ID transaksi dari sistem kamu, unik per akun. Huruf, angka, dan
. _ - :, maksimal 64 karakter. phonestringOpsional- Nomor WhatsApp untuk notifikasi pesanan. Bila kosong, memakai nomor di akun kamu.
Aman dikirim ulang (idempoten)
Kalau koneksi putus dan kamu tidak tahu hasilnya, kirim ulang request yang sama dengan reference_id yang sama. Kamu akan menerima pesanan yang sudah ada (HTTP 200, ORDER_EXISTS) dan saldo tidak dipotong dua kali. reference_id yang sama dengan isi berbeda ditolak (409).
curl -X POST https://josjistopup.com/api/v2/order \
-H "X-API-Key: API_KEY_KAMU" \
-H "Content-Type: application/json" \
-d '{
"product_id": 12,
"user_id": "12345678",
"zone_id": "1234",
"reference_id": "TRX-10001"
}'<?php
$ch = curl_init('https://josjistopup.com/api/v2/order');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . getenv('PILAR_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'product_id' => 12,
'user_id' => '12345678',
'zone_id' => '1234',
'reference_id' => 'TRX-10001',
]),
]);
$res = json_decode(curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($res['success'] ?? false) {
// simpan $res['data']['order_id'], status awal: $res['data']['status']
} else {
// $res['code'] contoh: INSUFFICIENT_BALANCE, VALIDATION_ERROR
}const res = await fetch('https://josjistopup.com/api/v2/order', {
method: 'POST',
headers: {
'X-API-Key': process.env.PILAR_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: 12,
user_id: '12345678',
zone_id: '1234',
reference_id: 'TRX-10001',
}),
});
const json = await res.json();
if (json.success) {
console.log(json.data.order_id, json.data.status);
} else {
console.error(json.code, json.message);
}{
"success": true,
"code": "ORDER_CREATED",
"message": "Pesanan diterima dan masuk antrean proses.",
"data": {
"order_id": "JOS261107K7Q2XZ9A",
"reference_id": "TRX-10001",
"status": "pending",
"product": {
"id": 12,
"name": "86 Diamond"
},
"category": {
"id": 1,
"name": "Mobile Legends"
},
"customer": {
"user_id": "12345678",
"zone_id": "1234"
},
"price": 23800,
"serial_number": null,
"note": null,
"created_at": "2026-11-07T10:20:41+07:00"
},
"meta": {
"balance": 226200
},
"request_id": "req_8f3a2c1d9b7e4a60"
}{
"success": false,
"code": "INSUFFICIENT_BALANCE",
"message": "Saldo tidak cukup. Butuh Rp 23.800, saldo kamu Rp 5.000.",
"data": {
"price": 23800,
"balance": 5000
},
"request_id": "req_8f3a2c1d9b7e4a60"
}Status pesanan
Status terbaru satu pesanan, beserta nomor SN bila sudah berhasil.
https://josjistopup.com/api/v2/statusorder_idstringOpsional- Nomor pesanan dari respons buat pesanan.
reference_idstringOpsional- Atau cari dengan reference_id kamu. Isi salah satu.
curl "https://josjistopup.com/api/v2/status?reference_id=TRX-10001" \
-H "X-API-Key: API_KEY_KAMU"{
"success": true,
"code": "OK",
"message": "Status pesanan berhasil diambil.",
"data": {
"order_id": "JOS261107K7Q2XZ9A",
"reference_id": "TRX-10001",
"status": "success",
"product": {
"id": 12,
"name": "86 Diamond"
},
"category": {
"id": 1,
"name": "Mobile Legends"
},
"customer": {
"user_id": "12345678",
"zone_id": "1234"
},
"price": 23800,
"serial_number": "RX81234567",
"note": null,
"created_at": "2026-11-07T10:20:41+07:00"
},
"request_id": "req_8f3a2c1d9b7e4a60"
}Nilai status
pending- Pesanan diterima, menunggu dikirim ke penyedia.
processing- Sedang diproses penyedia.
successFinal- Berhasil. Nomor SN ada di kolom
serial_number. refundedFinal- Gagal, saldo sudah dikembalikan. Alasan di kolom
notebila ada. canceledFinal- Dibatalkan.
Kalau belum memakai webhook, cek status tiap 15 sampai 30 detik dan berhenti begitu status final.
Riwayat pesanan
Pesanan akun kamu, terbaru dulu.
https://josjistopup.com/api/v2/ordersstatusstringOpsional- Saring:
pending,processing,success,refunded,canceled. fromdateOpsional- Mulai tanggal, format
YYYY-MM-DD. todateOpsional- Sampai tanggal, format
YYYY-MM-DD. pageintegerOpsional- Halaman, mulai dari 1.
per_pageintegerOpsional- 1 sampai 100. Bawaan 50.
curl "https://josjistopup.com/api/v2/orders?status=success&from=2026-11-01&per_page=20" \
-H "X-API-Key: API_KEY_KAMU"{
"success": true,
"code": "OK",
"message": "Riwayat pesanan berhasil diambil.",
"data": [
{
"order_id": "JOS261107K7Q2XZ9A",
"reference_id": "TRX-10001",
"status": "success",
"product": {
"id": 12,
"name": "86 Diamond"
},
"category": {
"id": 1,
"name": "Mobile Legends"
},
"customer": {
"user_id": "12345678",
"zone_id": "1234"
},
"price": 23800,
"serial_number": "RX81234567",
"note": null,
"created_at": "2026-11-07T10:20:41+07:00"
}
],
"meta": {
"page": 1,
"per_page": 20,
"total": 1,
"total_pages": 1
},
"request_id": "req_8f3a2c1d9b7e4a60"
}Webhook
Isi URL webhook di Akun > API supaya tidak perlu cek status berulang. Begitu pesanan API berstatus final (success atau refunded), kami kirim POST JSON ke URL itu. Berlaku untuk pesanan yang dibuat setelah URL diisi.
X-SignatureheaderWajib- HMAC-SHA256 dari body mentah dengan API key kamu sebagai kunci, dalam hex.
X-EventheaderWajiborder.successatauorder.refunded.Content-TypeheaderWajibapplication/json.
{
"event": "order.success",
"event_id": "evt_3f9a1c7e2b5d4a8f0e6c1b2a",
"sent_at": "2026-11-07T10:21:06+07:00",
"data": {
"order_id": "JOS261107K7Q2XZ9A",
"reference_id": "TRX-10001",
"status": "success",
"product": {
"id": 12,
"name": "86 Diamond"
},
"category": {
"id": 1,
"name": "Mobile Legends"
},
"customer": {
"user_id": "12345678",
"zone_id": "1234"
},
"price": 23800,
"serial_number": "RX81234567",
"note": null,
"created_at": "2026-11-07T10:20:41+07:00",
"updated_at": "2026-11-07T10:21:05+07:00",
"balance": 226200
}
}- Balas dengan HTTP 2xx dalam 10 detik. Kalau gagal, kami coba lagi 1, 5, 15, 30, lalu 60 menit kemudian.
- Webhook bisa terkirim lebih dari sekali. Pakai
event_idatauorder_id+statusuntuk mengabaikan duplikat. - Kalau status berubah lagi (misalnya success lalu refunded), webhook baru dikirim.
<?php
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, getenv('PILAR_API_KEY'));
if (!hash_equals($expected, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($body, true);
$order = $event['data'];
// Perbarui transaksi berdasarkan $order['reference_id'] dan $order['status']
http_response_code(200);
echo 'OK';import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhook/pilar', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto.createHmac('sha256', process.env.PILAR_API_KEY).update(req.body).digest('hex');
const got = req.get('X-Signature') || '';
if (got.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
// perbarui transaksi: event.data.reference_id, event.data.status
res.send('OK');
});Kode error
Saat gagal, success bernilai false dan alasannya ada di code. Error validasi juga menyertakan errors per parameter.
MISSING_API_KEYHTTP 401- API key tidak dikirim. Tambahkan header X-API-Key.
INVALID_API_KEYHTTP 401- API key salah atau sudah diganti. Salin ulang dari halaman Akun, menu API.
ACCOUNT_SUSPENDEDHTTP 403- Akun dinonaktifkan. Hubungi admin.
API_DISABLEDHTTP 403- Akses API masih mati. Nyalakan di halaman Akun, menu API.
TWO_FACTOR_REQUIREDHTTP 403- 2FA di akun belum aktif. Akses API hanya berlaku selama 2FA menyala.
IP_NOT_ALLOWEDHTTP 403- IP server kamu belum ada di whitelist.
VALIDATION_ERRORHTTP 400- Ada parameter yang salah atau kosong. Lihat errors.
INVALID_JSONHTTP 400- Body JSON tidak bisa dibaca.
METHOD_NOT_ALLOWEDHTTP 405- Metode HTTP salah, misalnya GET ke /order.
ENDPOINT_NOT_FOUNDHTTP 404- Alamat endpoint salah.
PRODUCT_NOT_FOUNDHTTP 404- product_id tidak ada.
PRODUCT_UNAVAILABLEHTTP 422- Produk sedang ditutup sementara. Coba produk lain.
INSUFFICIENT_BALANCEHTTP 422- Saldo kurang. Isi saldo lalu kirim ulang dengan reference_id yang sama.
REFERENCE_ID_CONFLICTHTTP 409- reference_id sudah dipakai untuk pesanan berbeda. Pakai reference_id baru.
ORDER_NOT_FOUNDHTTP 404- Pesanan tidak ada atau bukan milik akun kamu.
RATE_LIMITEDHTTP 429- Terlalu banyak request. Tunggu sesuai header Retry-After.
INTERNAL_ERRORHTTP 500- Kesalahan di server. Coba lagi; bila berulang, kirim request_id ke admin.
MAINTENANCEHTTP 503- Website sedang dalam perawatan. Coba lagi nanti.
Mulai pakai API
Nyalakan akses API, salin API key, lalu coba endpoint saldo.