Dokumentasi API

DITOPUP API v1.1 · Base URL https://ditopup.com/api/v1

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.

  1. Aktifkan langganan reseller di halaman Akun → Reseller.
  2. Buka Akun → API, klik Buat API Key, simpan API key dan callback secret.
  3. Daftarkan IP server kamu di whitelist (wajib), dan isi Callback URL untuk menerima update status.
  4. Isi saldo, ambil daftar produk, lalu kirim pesanan.
Base URLhttps://ditopup.com/api/v1
FormatJSON (UTF-8), request body application/json
Mata uangIDR (Rupiah, bilangan bulat)
WaktuISO 8601 UTC
Batas request120 request / menit / API key

Autentikasi

Kirim API key di header X-API-Key pada setiap request. Header Authorization: Bearer <key> juga diterima.

HTTP
GET /v1/balance HTTP/1.1
X-API-Key: dk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Jaga kerahasiaan API key. Simpan hanya di server, jangan di aplikasi mobile atau JavaScript browser. API key hanya ditampilkan sekali saat dibuat. Bila bocor, buat key baru dari halaman Akun dan key lama langsung tidak berlaku.

Format Respons

Semua respons memakai pola yang sama.

JSON — sukses
{
  "success": true,
  "data": { ... }
}
JSON — gagal
{
  "success": false,
  "error": {
    "code": "insufficient_balance",
    "message": "Saldo tidak mencukupi. Silakan deposit terlebih dahulu."
  }
}

Daftar Endpoint

GET/balanceCek saldo
GET/categoriesDaftar kategori
GET/productsDaftar produk & harga
POST/validateCek nama akun tujuan
POST/orderBuat transaksi
GET/order/{ref_id}Cek status transaksi
GET/ordersRiwayat transaksi
GET/deposit/channelsMetode isi saldo (QRIS, dll)
POST/depositBuat tagihan isi saldo / QRIS
GET/deposit/{ref_id}Cek status deposit
GET/depositsRiwayat deposit

Cek Saldo

GET/balance
cURL
curl https://ditopup.com/api/v1/balance \
  -H "X-API-Key: $API_KEY"
JSON
{
  "success": true,
  "data": {
    "balance": 500000,
    "currency": "IDR",
    "tier": "reseller",
    "reseller_until": "2026-11-23T03:00:00+00:00"
  }
}

Daftar Produk

GET/products
ParameterKeterangan
categoryOpsional. Nama kategori persis, mis. Game.
qOpsional. Kata kunci nama produk.
codeOpsional. Satu kode produk.
cURL
curl "https://ditopup.com/api/v1/products?category=Game" \
  -H "X-API-Key: $API_KEY"
JSON
{
  "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) dan zone (server/zone).
  • stock berisi angka untuk produk berstok terbatas, null berarti tidak dibatasi.
  • Harga bisa berubah. Ambil ulang daftar produk secara berkala (mis. tiap 15–60 menit) dan simpan di cache kamu.

Cek Nama Akun

POST/validate

Untuk produk dengan "validate": true, cek nama akun tujuan sebelum order. Opsional, tidak memotong saldo.

cURL
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"}'
JSON
{
  "success": true,
  "data": { "product": "MLB5", "target": "12345678", "zone": "1234", "name": "NamaPemain" }
}

Buat Transaksi

POST/order
FieldKeterangan
ref_idWajib. ID transaksi dari sistem kamu, unik, 4–64 karakter (huruf, angka, . - _).
productWajib. Kode produk dari /products.
targetID / nomor / email tujuan (sesuai fields).
zoneServer / Zone ID bila diperlukan.
qtyOpsional, default 1. Maksimal sesuai max_qty.
cURL
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"}'
JSON — 201 Created
{
  "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
  }
}
Aman dikirim ulang. Bila koneksi putus atau timeout, kirim ulang request dengan 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

GET/order/{ref_id}

Isi {ref_id} dengan ref_id kamu atau trx_id.

cURL
curl https://ditopup.com/api/v1/order/INV-20261024-0001 \
  -H "X-API-Key: $API_KEY"
statusArtiFinal
pendingTransaksi diterima, menunggu diprosesTidak
processingSedang diprosesTidak
successBerhasil. Cek sn (nomor seri/bukti) atau data (akun/lisensi/voucher)Ya
failedGagal, dana dikembalikan ke saldoYa
canceledDibatalkan, dana dikembalikan ke saldoYa

Untuk produk berisi akun/lisensi (mis. aplikasi premium), data dikirim di array data dan petunjuk tambahan di note.

Riwayat Transaksi

GET/orders
ParameterKeterangan
statusOpsional: pending, processing, success, failed, canceled
from / toOpsional, format YYYY-MM-DD
page / per_pageDefault 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.

GET/deposit/channels

Daftar metode yang aktif beserta batas nominal dan biaya. Kode metode dipakai di field channel.

cURL
curl https://ditopup.com/api/v1/deposit/channels \
  -H "X-API-Key: $API_KEY"
JSON
{
  "success": true,
  "data": [
    { "code": "QRIS", "name": "QRIS", "type": "qris", "min": 1000, "max": 10000000, "fee_flat": 0, "fee_percent": 0.7 }
  ]
}
POST/deposit
FieldKeterangan
ref_idWajib. ID deposit dari sistem kamu, unik, 4–64 karakter (huruf, angka, . - _).
amountWajib. Nominal saldo yang ingin ditambah (Rp, bilangan bulat). Harus di antara min dan max metode.
channelOpsional. Kode dari /deposit/channels. Kosong = pakai QRIS.
cURL
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"}'
JSON — 201 Created
{
  "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 menjadi expired.
  • Kirim ulang dengan ref_id yang sama tidak membuat tagihan baru, melainkan mengembalikan tagihan yang ada dengan "duplicate": true.
  • Batas pembuatan deposit: 200 per jam per akun.
GET/deposit/{ref_id}

Isi {ref_id} dengan ref_id kamu atau deposit_id. Dipakai untuk cek apakah sudah dibayar.

cURL
curl https://ditopup.com/api/v1/deposit/QR-20261108-0001 \
  -H "X-API-Key: $API_KEY"
statusArtiFinal
pendingMenunggu pembayaranTidak
successSudah dibayar, saldo sudah ditambahkan (credit terisi)Ya
failedDitolak atau dibatalkanYa
expiredTidak dibayar sampai batas waktuYa

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.

GET/deposits

Riwayat deposit yang dibuat lewat API. Parameter: status (pending, success, failed, expired), page, per_page (default 50, maksimal 100).

Ini isi saldo, bukan pembayaran langsung dari pelanggan. Uang yang masuk menambah saldo akun reseller kamu. Pesanan ke pelanggan tetap kamu buat lewat /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.

HTTP
POST /callback HTTP/1.1
Content-Type: application/json
X-Event: order.updated
X-Timestamp: 1761276650
X-Signature: 5f2b...c91e
JSON
{
  "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 2xx dalam 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 — verifikasi callback
<?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';
Node.js (Express) — verifikasi callback
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
<?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'];
}
Node.js
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);
Python
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 versi www yang berbeda.
  • Jangan mengisi header Host secara 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://, bukan http://, dan jangan lewat proxy yang mengubah host.
cURL — paksa HTTP/1.1
curl --http1.1 https://ditopup.com/api/v1/balance \
  -H "X-API-Key: $API_KEY"
PHP (cURL)
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
Python (requests, sudah HTTP/1.1)
r = requests.get('https://ditopup.com/api/v1/balance', headers={'X-API-Key': API_KEY}, timeout=30)
Node.js — client baru, tanpa reuse
const https = require('https');
const agent = new https.Agent({ keepAlive: false });
// fetch/axios: kirim { agent } agar tiap request memakai koneksi baru

Respons 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

HTTPcodeArti
400invalid_requestParameter kurang atau salah format
401unauthorizedAPI key tidak dikirim atau salah
402insufficient_balanceSaldo tidak mencukupi
403ip_not_whitelistedIP server belum didaftarkan
403reseller_requiredLangganan reseller tidak aktif
403key_disabled / account_disabledAkses dicabut admin
404product_unavailableKode produk salah, nonaktif, atau stok habis
404order_not_found / deposit_not_foundref_id tidak ditemukan
409duplicate_ref_idref_id sedang diproses bersamaan
422order_rejectedTransaksi ditolak. Dana sudah dikembalikan, detail ada di data
422target_not_foundID tujuan tidak ditemukan saat cek nama
422deposit_rejectedTagihan deposit ditolak penyedia pembayaran, coba lagi atau pakai metode lain
429rate_limitedTerlalu banyak request, ulangi setelah header Retry-After
503api_disabled / service_unavailableLayanan 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.