Langsung ke konten
JosJis Topup

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

  1. 1Masuk atau buat akun, lalu isi saldo. Semua pesanan API dibayar dari saldo akun.
  2. 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.
  3. 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.

Header
X-API-Key: API_KEY_KAMU
  • Alternatif: Authorization: Bearer API_KEY_KAMU. Untuk klien yang tidak bisa mengirim header, parameter api_key di 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 header Retry-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.

Berhasil
{
    "success": true,
    "code": "OK",
    "message": "Saldo berhasil diambil.",
    "data": {
        "balance": 250000
    },
    "request_id": "req_8f3a2c1d9b7e4a60"
}
Gagal
{
    "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.

GEThttps://josjistopup.com/api/v2/balance
Contoh request
curl https://josjistopup.com/api/v2/balance \
  -H "X-API-Key: API_KEY_KAMU"
Respons 200
{
    "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).

GEThttps://josjistopup.com/api/v2/categories
Query
typestringOpsional
Saring per jenis: game, voucher, pulsa, pln, live-app.
searchstringOpsional
Cari berdasarkan nama, maksimal 60 karakter.
Contoh request
curl "https://josjistopup.com/api/v2/categories?type=game&search=mobile" \
  -H "X-API-Key: API_KEY_KAMU"
Respons 200
{
    "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.

GEThttps://josjistopup.com/api/v2/products
Query
category_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.
Contoh request
curl "https://josjistopup.com/api/v2/products?category_id=1&per_page=50" \
  -H "X-API-Key: API_KEY_KAMU"
Respons 200
{
    "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.

POSThttps://josjistopup.com/api/v2/order
Body
product_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 fields di /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
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
<?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
}
Node.js (18+)
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);
}
Respons 201
{
    "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"
}
Respons 422
{
    "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.

GEThttps://josjistopup.com/api/v2/status
Query
order_idstringOpsional
Nomor pesanan dari respons buat pesanan.
reference_idstringOpsional
Atau cari dengan reference_id kamu. Isi salah satu.
Contoh request
curl "https://josjistopup.com/api/v2/status?reference_id=TRX-10001" \
  -H "X-API-Key: API_KEY_KAMU"
Respons 200
{
    "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 note bila 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.

GEThttps://josjistopup.com/api/v2/orders
Query
statusstringOpsional
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.
Contoh request
curl "https://josjistopup.com/api/v2/orders?status=success&from=2026-11-01&per_page=20" \
  -H "X-API-Key: API_KEY_KAMU"
Respons 200
{
    "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.

Header
X-SignatureheaderWajib
HMAC-SHA256 dari body mentah dengan API key kamu sebagai kunci, dalam hex.
X-EventheaderWajib
order.success atau order.refunded.
Content-TypeheaderWajib
application/json.
Isi webhook
{
    "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_id atau order_id + status untuk mengabaikan duplikat.
  • Kalau status berubah lagi (misalnya success lalu refunded), webhook baru dikirim.
Verifikasi (PHP)
<?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';
Verifikasi (Node.js + Express)
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.

Buka pengaturan API