KiosMitu API
Dokumentasi teknis untuk mendapatkan data user, melakukan transfer bank/e-wallet, dan membuat custom order. Semua request dan response menggunakan JSON—kecuali QR image yang dapat berupa data URI.
Base URL
https://kiosmitu.com/api/v1
https://kiosmitu.com/api/v1
Auth
Bearer token
Dari admin
Mutasi tertentu
Bearer + signature
Nonce 60 detik
Format
application/json
UTF-8
Kredensial API — hubungi admin
API ini tidak menyediakan endpoint login. Untuk mendapatkan access_token dan public_key, hubungi admin KiosMitu. Jangan membuat atau mengirim kredensial sendiri.
- access_token = Bearer token; rahasia.
- public_key = dipakai untuk membuat
X-Nonce-Hash; diberikan admin. - Jangan commit token ke repository atau menaruhnya di frontend publik.
Signature request
Endpoint dengan badge Bearer + Signature wajib mengirim dua header tambahan.
Rumus
string_to_hash = X-Nonce + ":" + public_key X-Nonce-Hash = SHA-256(string_to_hash) // lowercase hex
X-Nonce adalah Unix timestamp dalam detik. Server menolak nonce yang lebih tua dari 60 detik. Buat nonce baru untuk setiap request.
Headers minimum
Authorization: Bearer <access_token> X-Nonce: 1760000000 X-Nonce-Hash: 8f14e45fceea167a5a36dedd4bea2543...
Contoh PHP
<?php
$publicKey = 'PUBLIC_KEY_DARI_ADMIN';
$token = 'ACCESS_TOKEN_DARI_ADMIN';
$nonce = (string) time();
$hash = hash('sha256', $nonce.':'.$publicKey);
$headers = [
'Authorization: Bearer '.$token,
'X-Nonce: '.$nonce,
'X-Nonce-Hash: '.$hash,
'Content-Type: application/json',
];
Contoh JavaScript
const publicKey = 'PUBLIC_KEY_DARI_ADMIN';
const token = 'ACCESS_TOKEN_DARI_ADMIN';
const nonce = Math.floor(Date.now() / 1000).toString();
const data = new TextEncoder().encode(nonce + ':' + publicKey);
const digest = await crypto.subtle.digest('SHA-256', data);
const hash = Array.from(new Uint8Array(digest))
.map(b => b.toString(16).padStart(2, '0')).join('');
const headers = {
Authorization: 'Bearer ' + token,
'X-Nonce': nonce,
'X-Nonce-Hash': hash,
'Content-Type': 'application/json',
};
Contoh cURL lengkap
NONCE=$(date +%s)
PUBLIC_KEY='PUBLIC_KEY_DARI_ADMIN'
TOKEN='ACCESS_TOKEN_DARI_ADMIN'
HASH=$(printf '%s:%s' "$NONCE" "$PUBLIC_KEY" | shasum -a 256 | awk '{print $1}')
curl -X POST 'https://kiosmitu.com/api/v1/transfer/inquiry' \
-H "Authorization: Bearer $TOKEN" \
-H "X-Nonce: $NONCE" \
-H "X-Nonce-Hash: $HASH" \
-H 'Content-Type: application/json' \
-d '{"type":"bank","bankcode":"BCA","accountnumber":"1234567890","amount":50000}'
Response jika signature gagal
400 — header tidak lengkap
{"error":"Missing nonce or nonce hash"}
400 — hash salah
{"error":"Invalid nonce"}
400 — nonce kedaluwarsa
{"error":"Nonce expired61","message":"Nonce expired61","data":"..."}
Format response
Envelope standar
{"status":"success","data":{}}
Dipakai transfer dan custom order.
User resource
{"email":"...","balance":150000}
GET /user mengembalikan object langsung, tanpa data.
Validation 422
{"message":"...","errors":{"field":["..."]}}
Signature 400
{"error":"Invalid nonce"}
Tidak memakai envelope status.
/api/v1/user
Bearer
Data user & saldo
Mengembalikan profil dan saldo user yang sedang login. Tidak butuh signature.
Request
GET /api/v1/user Authorization: Bearer <access_token>
200 — success
{
"email": "[email protected]",
"name": "Nama User",
"balance": 150000,
"phone": "081234567890",
"level": 0,
"pin_exist": true,
"verified": true,
"referral": "REF123",
"is_updated": 1,
"last_changed": "2026-11-01 10:00:00",
"trx_count": 25,
"ai_kredit": 0
}
401 — unauthenticated
{"message":"Unauthenticated."}
Catatan: di codebase ini endpoint saldo adalah /user, bukan /user/me.
/api/v1/transfer/banksBearerDaftar bank
Daftar bank tujuan transfer. Jika penyedia layanan sedang tidak dapat dihubungi, endpoint fail-soft mengembalikan array kosong.
200 — success
{"status":"success","data":[{"kodeBank":"BCA","namaBank":"Bank Central Asia"}]}
200 — sumber bank kosong
{"status":"success","data":[]}
401 — unauthenticated
{"message":"Unauthenticated."}
/api/v1/transfer/ewalletsBearerDaftar e-wallet
Daftar e-wallet tujuan transfer. Fail-soft seperti daftar bank.
200 — success
{"status":"success","data":[{"kodeBank":"DANA","namaBank":"DANA"}]}
200 — kosong
{"status":"success","data":[]}
401 — unauthenticated
{"message":"Unauthenticated."}
/api/v1/transfer/inquiryBearer + SignatureInquiry transfer
Validasi rekening/ewallet dan kunci nominal. Inquiry hanya berlaku 60 detik.
Headers
Authorization: Bearer <access_token> X-Nonce: 1760000000 X-Nonce-Hash: <sha256(nonce + ":" + public_key)> Content-Type: application/json
Body parameter
| Field | Tipe | Wajib | Aturan |
|---|---|---|---|
| type | string | ya | bank atau ewallet |
| bankcode | string | ya | max 20, kode dari daftar bank/e-wallet |
| accountnumber | string | ya | max 50, spasi dihapus server |
| amount | integer | ya | minimal 30.000 |
Request
{
"type": "bank",
"bankcode": "BCA",
"accountnumber": "1234567890",
"amount": 50000
}
200 — inquiry berhasil
{
"status": "success",
"data": {
"public_id": "TF-1232026110712000001",
"accountname": "NAMA PENERIMA",
"fee": 3500,
"total": 53500,
"expires_at": "2026-11-07 12:01:00"
}
}
Gunakan nilai fee, total, dan expires_at langsung dari response. Saldo harus menutup total.
400 — saldo kurang dari amount
{"status":"fail","message":"Saldo tidak cukup"}
400 — batas 15 transfer processing
{"status":"fail","message":"Maksimal 15 transfer sedang diproses."}
400 — saldo kurang untuk total termasuk fee
{"status":"fail","message":"Saldo tidak cukup untuk total transfer (termasuk fee)."}
400 — pengecekan rekening gagal
{"status":"fail","message":"Inquiry gagal: <response_desc|rd|unknown>"}
400 — signature gagal
{"error":"Missing nonce or nonce hash"}
422 — validasi
{
"message": "Tipe tujuan wajib diisi. (and 3 more errors)",
"errors": {
"type": ["Tipe tujuan wajib diisi."],
"bankcode": ["Kode bank/ewallet wajib dipilih."],
"accountnumber": ["Nomor rekening/HP wajib diisi."],
"amount": ["Nominal wajib diisi."]
}
}
401 / 429
{"message":"Unauthenticated."}
{"message":"Too Many Attempts."}
/api/v1/transfer/payBearer + SignatureEksekusi transfer
Memotong saldo dan memproses transfer. Gunakan PIN 6 digit yang sama dengan PIN akun.
Body
{"public_id":"TF-...","pin":"123456"}
200 — berhasil masuk processing
{"status":"success","message":"Dalam proses","data":{"public_id":"TF-...","status":"processing"}}
200 — idempotent, sudah pernah diproses
{"status":"success","message":"Sudah diproses","data":{"public_id":"TF-...","status":"success"}}
404 — public_id bukan milik user/tidak ada
{"status":"fail","message":"Transfer tidak ditemukan"}
400 — inquiry kedaluwarsa
{"status":"fail","message":"Inquiry kedaluwarsa (>60 detik). Silakan inquiry ulang."}
400 — saldo tidak cukup / batas processing
{"status":"fail","message":"Saldo tidak cukup"}
{"status":"fail","message":"Maksimal 15 transfer sedang diproses."}
400 — PIN salah
{"message":"Pin Salah. Sisa percobaan: 9."}
429 — PIN terkunci
{"message":"PIN terkunci selama 20 menit karena terlalu banyak kesalahan."}
422 — validasi
{
"message": "ID transfer wajib diisi. (and 1 more error)",
"errors": {
"public_id": ["ID transfer wajib diisi."],
"pin": ["PIN wajib diisi."]
}
}
400 signature / 401 / 429 throttle
{"error":"Invalid nonce"}
{"message":"Unauthenticated."}
{"message":"Too Many Attempts."}
PIN salah 10 kali mengunci PIN selama 20 menit. Status akhir transfer bisa dicek lewat detail transfer; status inquiry tidak muncul di list.
/api/v1/transfersBearerRiwayat transfer
Query
GET /api/v1/transfers?search=TF-&status=success&page=1 Authorization: Bearer <access_token>
`search` mencari public_id, bank, nomor rekening, nama rekening, serial, dan payment ref. `status` opsional. Response dipaginasi 20 item.
200 — success
{
"status": "success",
"data": [
{
"public_id": "TF-...",
"type": "bank",
"bankcode": "BCA",
"bankname": "Bank Central Asia",
"accountnumber": "1234567890",
"accountname": "NAMA PENERIMA",
"amount": 50000,
"fee": 3500,
"total": 53500,
"status": "success",
"fail_reason": null,
"serial": "SN123",
"is_refunded": false,
"refund_amount": 0,
"refunded_at": null,
"saldo_before": 150000,
"saldo_after": 96500,
"created_at": "2026-11-07T12:00:00.000000Z"
}
],
"links": {},
"meta": {}
}
401
{"message":"Unauthenticated."}
/api/v1/transfer/{public_id}BearerDetail transfer
200 — success
{"status":"success","data":{"public_id":"TF-...","status":"success","amount":50000,"fee":3500,"total":53500,"serial":"SN123","is_refunded":false}}
404 — tidak ditemukan / masih inquiry
{"status":"fail","message":"Transfer tidak ditemukan"}
401
{"message":"Unauthenticated."}
/api/v1/ordercustom/createBearer + SignatureBuat custom order
Membuat tagihan QRIS dan menambahkan saldo user setelah dibayar. QRIS berlaku 15 menit.
Body parameter
| Field | Tipe | Wajib | Aturan |
|---|---|---|---|
| trxid | string | ya | max 255, unik per user |
| nominal | integer | ya | 1 s.d. 10.000.000 |
| callback_url | string/null | tidak | HTTPS port 443, host publik, tidak IP/private, resolve |
| detail | string/null | tidak | max 1000 |
Nominal tagihan
Gunakan nilai price, real_price, dan fee_total langsung dari response.
Request
{
"trxid": "INV-20261107-001",
"nominal": 50000,
"callback_url": "https://example.com/webhook/ordercustom",
"detail": "Top up saldo custom"
}
200 — QRIS berhasil dibuat
{
"status": "success",
"data": {
"public_id": "OC-...",
"trxid": "INV-20261107-001",
"price": 50700,
"real_price": 50000,
"fee_total": 700,
"commercial": false,
"status": "pending",
"qr_url": "data:image/png;base64,...",
"detail": "Top up saldo custom",
"description": null,
"created_at": "2026-11-07T12:00:00.000000Z"
}
}
200 — pembuatan QRIS gagal (HTTP tetap 200)
{
"status": "fail",
"message": "Gagal menghubungi payment gateway",
"data": {
"public_id": "OC-...",
"trxid": "INV-20261107-001",
"price": 50700,
"real_price": 50000,
"fee_total": 700,
"commercial": false,
"status": "pending",
"qr_url": null,
"detail": "Top up saldo custom",
"description": "Gagal: Gagal menghubungi payment gateway",
"created_at": "2026-11-07T12:00:00.000000Z"
}
}
422 — validasi trxid duplikat
{"message":"The trxid has already been taken.","errors":{"trxid":["The trxid has already been taken."]}}
422 — nominal tidak valid
{"message":"The nominal field must be between 1 and 10000000.","errors":{"nominal":["The nominal field must be between 1 and 10000000."]}}
422 — callback URL
{"message":"Callback URL harus https port 443.","errors":{"callback_url":["Callback URL harus https port 443."]}}
Pesan lain: Callback URL terlalu panjang., Callback URL tidak boleh IP., Callback URL tidak bisa di-resolve., Callback URL tidak boleh private range.
400 signature / 401 / 429
{"error":"Nonce expired61","message":"Nonce expired61","data":"..."}
{"message":"Unauthenticated."}
{"message":"Too Many Attempts."}
/api/v1/ordercustom/{public_id}BearerCek custom order
Status order dapat berubah menjadi completed setelah pembayaran terdeteksi.
200 — pending
{"status":"success","data":{"public_id":"OC-...","trxid":"INV-...","price":50700,"real_price":50000,"fee_total":700,"commercial":false,"status":"pending","qr_url":"data:image/png;base64,...","detail":"Top up saldo custom","description":null,"created_at":"..."}}
200 — completed
{"status":"success","data":{"public_id":"OC-...","trxid":"INV-...","price":50700,"real_price":50000,"fee_total":700,"commercial":false,"status":"completed","qr_url":"data:image/png;base64,...","detail":"Top up saldo custom","description":"Order Custom: ...","created_at":"..."}}
200 — expired (HTTP tetap 200)
{"status":"fail","message":"QRIS kedaluwarsa","data":{"public_id":"OC-...","status":"expired"}}
200 — underpaid (HTTP tetap 200)
{"status":"fail","message":"Kurang bayar — hubungi admin","data":{"public_id":"OC-...","status":"underpaid"}}
200 — penyedia pembayaran tidak dapat dihubungi
{"status":"success","data":{"public_id":"OC-...","status":"pending"}}
404 — tidak ditemukan / bukan milik user
{"status":"fail","message":"Order tidak ditemukan"}
401 / 429
{"message":"Unauthenticated."}
{"message":"Too Many Attempts."}
callback_url milik AndaWebhookWebhook custom order lunas
Dikirim sekali setelah order dibayar. Tidak ada retry otomatis.
Headers
X-Signature: <hmac_sha256> X-Timestamp: 1760000000 Content-Type: application/json
Payload
{
"public_id": "OC-...",
"trxid": "INV-20261107-001",
"status": "paid",
"price": 50700,
"real_price": 50000,
"fee_total": 700,
"commercial": false,
"paid_amount": 50700,
"paid_at": "2026-11-07T12:00:00+07:00",
"issued_at": 1760000000,
"idempotency_key": "oc..."
}
Verifikasi signature
key = SHA-256("ordercustom-webhook-v1:" + public_key)
signature = HMAC-SHA256(raw_request_body, key)
Verifikasi memakai raw body byte-per-byte. Jangan decode JSON lalu encode ulang sebelum verifikasi. Balas 2xx untuk menandai webhook diterima.
Catatan keamanan
- Callback URL wajib HTTPS, port 443, host publik, dan tidak boleh private range/IP.
- Webhook tidak di-retry; simpan idempotency key untuk mencegah pemrosesan ganda di sisi penerima.
Error & rate limit
| Status | Kapan | Contoh body |
|---|---|---|
| 400 | Signature, aturan bisnis, saldo, batas transfer | {"status":"fail","message":"..."} atau {"error":"..."} |
| 401 | Token tidak ada/invalid | {"message":"Unauthenticated."} |
| 404 | Resource tidak ada / bukan milik user | {"status":"fail","message":"... tidak ditemukan"} |
| 422 | Validasi request | {"message":"...","errors":{...}} |
| 429 | Rate limit, PIN lockout | {"message":"Too Many Attempts."} |
Rate limit
| Aksi | Batas |
|---|---|
| Transfer inquiry | 10 / menit |
| Transfer pay | 10 / menit |
| Custom order create | 30 / menit |
| Custom order check | 60 / menit |
Rate limit dapat berbeda di masa depan. Jika menerima 429, hormati header Retry-After atau tunggu sebelum mencoba lagi.