Mulai
DITOPUP API memungkinkan website, bot, atau aplikasi kamu menjual produk DITOPUP secara otomatis (H2H). Saldo yang dipakai adalah saldo akun kamu di DITOPUP, dengan harga reseller.
- Aktifkan langganan reseller di halaman Akun → Reseller.
- Buka Akun → API, klik Buat API Key, simpan API key dan callback secret.
- Daftarkan IP server kamu di whitelist (wajib), dan isi Callback URL untuk menerima update status.
- Isi saldo, ambil daftar produk, lalu kirim pesanan.
| Base URL | https://ditopup.com/api/v1 |
| Format | JSON (UTF-8), request body application/json |
| Mata uang | IDR (Rupiah, bilangan bulat) |
| Waktu | ISO 8601 UTC |
| Batas request | 120 request / menit / API key |
Autentikasi
Kirim API key di header X-API-Key pada setiap request. Header Authorization: Bearer <key> juga diterima.
GET /v1/balance HTTP/1.1
X-API-Key: dk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/jsonFormat Respons
Semua respons memakai pola yang sama.
{
"success": true,
"data": { ... }
}{
"success": false,
"error": {
"code": "insufficient_balance",
"message": "Saldo tidak mencukupi. Silakan deposit terlebih dahulu."
}
}Daftar Endpoint
/balanceCek saldo/categoriesDaftar kategori/productsDaftar produk & harga/validateCek nama akun tujuan/orderBuat transaksi/order/{ref_id}Cek status transaksi/ordersRiwayat transaksi/deposit/channelsMetode isi saldo (QRIS, dll)/depositBuat tagihan isi saldo / QRIS/deposit/{ref_id}Cek status deposit/depositsRiwayat depositCek Saldo
/balancecurl https://ditopup.com/api/v1/balance \
-H "X-API-Key: $API_KEY"{
"success": true,
"data": {
"balance": 500000,
"currency": "IDR",
"tier": "reseller",
"reseller_until": "2026-11-23T03:00:00+00:00"
}
}Daftar Produk
/products| Parameter | Keterangan |
|---|---|
category | Opsional. Nama kategori persis, mis. Game. |
q | Opsional. Kata kunci nama produk. |
code | Opsional. Satu kode produk. |
curl "https://ditopup.com/api/v1/products?category=Game" \
-H "X-API-Key: $API_KEY"{
"success": true,
"data": [
{
"code": "MLB5",
"product": "Mobile Legends",
"name": "86 Diamonds",
"category": "Game",
"price": 19500,
"normal_price": 19500,
"promo": false,
"status": "available",
"stock": null,
"max_qty": 1,
"fields": [
{ "key": "target", "label": "User ID", "required": true },
{ "key": "zone", "label": "Server / Zone ID", "required": true }
],
"validate": true,
"image": null
}
]
}- code dipakai saat order. Kode bersifat tetap, tapi produk bisa nonaktif sewaktu-waktu.
- price adalah harga yang dipotong dari saldo kamu per unit (sudah termasuk promo bila ada).
- fields menunjukkan data yang wajib dikirim saat order:
target(ID/nomor tujuan) danzone(server/zone). - stock berisi angka untuk produk berstok terbatas,
nullberarti tidak dibatasi. - Harga bisa berubah. Ambil ulang daftar produk secara berkala (mis. tiap 15–60 menit) dan simpan di cache kamu.
Cek Nama Akun
/validateUntuk produk dengan "validate": true, cek nama akun tujuan sebelum order. Opsional, tidak memotong saldo.
curl -X POST https://ditopup.com/api/v1/validate \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"product":"MLB5","target":"12345678","zone":"1234"}'{
"success": true,
"data": { "product": "MLB5", "target": "12345678", "zone": "1234", "name": "NamaPemain" }
}Buat Transaksi
/order| Field | Keterangan |
|---|---|
ref_id | Wajib. ID transaksi dari sistem kamu, unik, 4–64 karakter (huruf, angka, . - _). |
product | Wajib. Kode produk dari /products. |
target | ID / nomor / email tujuan (sesuai fields). |
zone | Server / Zone ID bila diperlukan. |
qty | Opsional, default 1. Maksimal sesuai max_qty. |
curl -X POST https://ditopup.com/api/v1/order \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"ref_id":"INV-20261024-0001","product":"MLB5","target":"12345678","zone":"1234"}'{
"success": true,
"data": {
"ref_id": "INV-20261024-0001",
"trx_id": "TS2610241030451234",
"product": "MLB5",
"product_name": "Mobile Legends 86 Diamonds",
"item_name": "86 Diamonds",
"qty": 1,
"target": "12345678",
"zone": "1234",
"price": 19500,
"total": 19500,
"refunded": 0,
"status": "processing",
"message": "",
"sn": null,
"data": [],
"note": null,
"created_at": "2026-10-24T03:30:45+00:00",
"updated_at": "2026-10-24T03:30:46+00:00",
"balance": 480500
}
}ref_id yang sama. Sistem tidak akan membuat transaksi dobel, dan mengembalikan transaksi yang sudah ada dengan "duplicate": true.Saldo langsung dipotong saat transaksi dibuat. Bila transaksi gagal, dana dikembalikan otomatis ke saldo (lihat field refunded).
Status Transaksi
/order/{ref_id}Isi {ref_id} dengan ref_id kamu atau trx_id.
curl https://ditopup.com/api/v1/order/INV-20261024-0001 \
-H "X-API-Key: $API_KEY"| status | Arti | Final |
|---|---|---|
pending | Transaksi diterima, menunggu diproses | Tidak |
processing | Sedang diproses | Tidak |
success | Berhasil. Cek sn (nomor seri/bukti) atau data (akun/lisensi/voucher) | Ya |
failed | Gagal, dana dikembalikan ke saldo | Ya |
canceled | Dibatalkan, dana dikembalikan ke saldo | Ya |
Untuk produk berisi akun/lisensi (mis. aplikasi premium), data dikirim di array data dan petunjuk tambahan di note.
Riwayat Transaksi
/orders| Parameter | Keterangan |
|---|---|
status | Opsional: pending, processing, success, failed, canceled |
from / to | Opsional, format YYYY-MM-DD |
page / per_page | Default 1 / 50, maksimal 100 per halaman |
Deposit / QRIS Isi Saldo
Buat tagihan isi saldo lewat API, mis. satu QRIS untuk setiap order pelanggan di bot kamu. Setelah dibayar, saldo akun kamu otomatis bertambah sebesar amount, lalu kamu bisa memakainya untuk /order.
/deposit/channelsDaftar metode yang aktif beserta batas nominal dan biaya. Kode metode dipakai di field channel.
curl https://ditopup.com/api/v1/deposit/channels \
-H "X-API-Key: $API_KEY"{
"success": true,
"data": [
{ "code": "QRIS", "name": "QRIS", "type": "qris", "min": 1000, "max": 10000000, "fee_flat": 0, "fee_percent": 0.7 }
]
}/deposit| Field | Keterangan |
|---|---|
ref_id | Wajib. ID deposit dari sistem kamu, unik, 4–64 karakter (huruf, angka, . - _). |
amount | Wajib. Nominal saldo yang ingin ditambah (Rp, bilangan bulat). Harus di antara min dan max metode. |
channel | Opsional. Kode dari /deposit/channels. Kosong = pakai QRIS. |
curl -X POST https://ditopup.com/api/v1/deposit \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"ref_id":"QR-20261108-0001","amount":25000,"channel":"QRIS"}'{
"success": true,
"data": {
"ref_id": "QR-20261108-0001",
"deposit_id": "TSD2611081030451234",
"channel": "QRIS",
"channel_name": "QRIS",
"amount": 25000,
"fee": 175,
"pay_amount": 25175,
"credit": 0,
"status": "pending",
"qr_link": "https://.../qr/abc123.png",
"pay_url": "https://.../pay/abc123",
"expired_at": "2026-11-08T04:00:00+00:00",
"created_at": "2026-11-08T03:30:45+00:00",
"updated_at": "2026-11-08T03:30:46+00:00"
}
}- qr_link adalah gambar QR untuk ditampilkan ke pembayar, pay_url halaman bayar. Salah satunya bisa kosong tergantung metode.
- pay_amount adalah nominal yang harus dibayar pembayar (sudah termasuk biaya bila ada). amount adalah saldo yang masuk.
- Tagihan kedaluwarsa pada
expired_at. Setelah itu status menjadiexpired. - Kirim ulang dengan
ref_idyang sama tidak membuat tagihan baru, melainkan mengembalikan tagihan yang ada dengan"duplicate": true. - Batas pembuatan deposit: 200 per jam per akun.
/deposit/{ref_id}Isi {ref_id} dengan ref_id kamu atau deposit_id. Dipakai untuk cek apakah sudah dibayar.
curl https://ditopup.com/api/v1/deposit/QR-20261108-0001 \
-H "X-API-Key: $API_KEY"| status | Arti | Final |
|---|---|---|
pending | Menunggu pembayaran | Tidak |
success | Sudah dibayar, saldo sudah ditambahkan (credit terisi) | Ya |
failed | Ditolak atau dibatalkan | Ya |
expired | Tidak dibayar sampai batas waktu | Ya |
Sistem juga mengirim callback deposit.updated saat status berubah (lihat bagian Callback). Polling tiap 3–5 detik cukup untuk bot, dan jangan lebih sering dari itu.
/depositsRiwayat deposit yang dibuat lewat API. Parameter: status (pending, success, failed, expired), page, per_page (default 50, maksimal 100).
/order setelah deposit berstatus success.Callback (Webhook)
Setiap status transaksi atau deposit berubah, sistem mengirim POST ke Callback URL kamu. Callback URL wajib https://. Header X-Event berisi order.updated atau deposit.updated.
POST /callback HTTP/1.1
Content-Type: application/json
X-Event: order.updated
X-Timestamp: 1761276650
X-Signature: 5f2b...c91e{
"event": "order.updated",
"data": { ...sama dengan respons /order/{ref_id}... }
}
{
"event": "deposit.updated",
"data": { ...sama dengan respons /deposit/{ref_id}, plus "balance" saat success... }
}- Verifikasi setiap callback:
X-Signature= HMAC-SHA256 dari raw body dengan callback secret kamu (hex). - Balas dengan HTTP
2xxdalam 10 detik. Selain itu dianggap gagal dan dikirim ulang hingga 5 kali (1, 5, 15, 30, 60 menit). - Callback bisa datang lebih dari sekali atau tidak berurutan. Proses berdasarkan
ref_id+status, dan abaikan bila status di sistem kamu sudah final. - Tetap sediakan pengecekan berkala lewat
/order/{ref_id}sebagai cadangan. - Tombol Tes Callback di Akun → API mengirim event
ping.
<?php
$secret = 'whsec_xxxxxxxxxxxxxxxx';
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if (!hash_equals(hash_hmac('sha256', $raw, $secret), $sig)) {
http_response_code(401);
exit('invalid signature');
}
$event = json_decode($raw, true);
if ($event['event'] === 'order.updated') {
$o = $event['data'];
// update transaksi $o['ref_id'] di database kamu menjadi $o['status']
}
http_response_code(200);
echo 'OK';const crypto = require('crypto');
app.post('/callback', express.raw({ type: 'application/json' }), (req, res) => {
const sig = crypto.createHmac('sha256', process.env.CALLBACK_SECRET).update(req.body).digest('hex');
if (sig !== req.get('X-Signature')) return res.status(401).send('invalid signature');
const event = JSON.parse(req.body);
// proses event.data.ref_id & event.data.status
res.send('OK');
});Contoh Kode
<?php
function api($method, $path, $body = null) {
$ch = curl_init('https://ditopup.com/api/v1' . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('API_KEY'), 'Content-Type: application/json'],
]);
if ($body) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
return $res;
}
$order = api('POST', '/order', [
'ref_id' => 'INV-' . time(),
'product' => 'MLB5',
'target' => '12345678',
'zone' => '1234',
]);
if ($order['success']) {
echo $order['data']['status'];
} else {
echo $order['error']['code'] . ': ' . $order['error']['message'];
}const BASE = 'https://ditopup.com/api/v1';
async function api(method, path, body) {
const r = await fetch(BASE + path, {
method,
headers: { 'X-API-Key': process.env.API_KEY, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
return r.json();
}
const order = await api('POST', '/order', {
ref_id: 'INV-' + Date.now(),
product: 'MLB5',
target: '12345678',
zone: '1234',
});
console.log(order.success ? order.data.status : order.error);import os, time, requests
BASE = 'https://ditopup.com/api/v1'
H = {'X-API-Key': os.environ['API_KEY']}
r = requests.post(BASE + '/order', headers=H, timeout=60, json={
'ref_id': f'INV-{int(time.time())}',
'product': 'MLB5',
'target': '12345678',
'zone': '1234',
}).json()
print(r['data']['status'] if r['success'] else r['error'])Masalah Koneksi (421)
Bila muncul 421 Misdirected Request atau error SNI/TLS, penyebabnya hampir selalu cara client menyambung ke server, bukan API key atau akunmu. Periksa urut berikut.
- Pakai persis Base URL ini:
https://ditopup.com/api/v1. Jangan ganti ke alamat IP, domain lain, atau versiwwwyang berbeda. - Jangan mengisi header
Hostsecara manual. Biarkan library HTTP mengisinya dari URL. - Satu koneksi HTTP/2 yang dipakai ulang untuk beberapa domain bisa ditolak server. Paksa HTTP/1.1 atau buat client baru per domain.
- Pakai
https://, bukanhttp://, dan jangan lewat proxy yang mengubah host.
curl --http1.1 https://ditopup.com/api/v1/balance \
-H "X-API-Key: $API_KEY"curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);r = requests.get('https://ditopup.com/api/v1/balance', headers={'X-API-Key': API_KEY}, timeout=30)const https = require('https');
const agent = new https.Agent({ keepAlive: false });
// fetch/axios: kirim { agent } agar tiap request memakai koneksi baruRespons API menyertakan header X-API-Base-URL berisi alamat resmi yang harus dipakai. Bila masih terjadi setelah semua di atas, kirim ke admin: waktu kejadian, URL persis, dan library yang dipakai.
Kode Error
| HTTP | code | Arti |
|---|---|---|
| 400 | invalid_request | Parameter kurang atau salah format |
| 401 | unauthorized | API key tidak dikirim atau salah |
| 402 | insufficient_balance | Saldo tidak mencukupi |
| 403 | ip_not_whitelisted | IP server belum didaftarkan |
| 403 | reseller_required | Langganan reseller tidak aktif |
| 403 | key_disabled / account_disabled | Akses dicabut admin |
| 404 | product_unavailable | Kode produk salah, nonaktif, atau stok habis |
| 404 | order_not_found / deposit_not_found | ref_id tidak ditemukan |
| 409 | duplicate_ref_id | ref_id sedang diproses bersamaan |
| 422 | order_rejected | Transaksi ditolak. Dana sudah dikembalikan, detail ada di data |
| 422 | target_not_found | ID tujuan tidak ditemukan saat cek nama |
| 422 | deposit_rejected | Tagihan deposit ditolak penyedia pembayaran, coba lagi atau pakai metode lain |
| 429 | rate_limited | Terlalu banyak request, ulangi setelah header Retry-After |
| 503 | api_disabled / service_unavailable | Layanan sedang tidak tersedia |
Untuk error 5xx atau timeout saat membuat transaksi, jangan langsung anggap gagal. Cek dulu lewat /order/{ref_id} atau kirim ulang dengan ref_id yang sama.
Ketentuan Pemakaian
- API hanya untuk reseller aktif dan dipakai untuk menjual kembali ke pelanggan kamu sendiri.
- Kamu bertanggung jawab atas data tujuan yang dikirim. Transaksi yang sudah sukses ke tujuan yang salah tidak bisa dibatalkan.
- Dilarang membebani sistem secara berlebihan, menguji celah keamanan, atau membagikan API key ke pihak lain.
- Akses API bisa dicabut bila terjadi penyalahgunaan. Ketentuan lain mengikuti Syarat & Ketentuan.