Premilo Reseller API v1
Jualan stok Premilo langsung dari bot atau web kamu
Satu API buat ambil katalog, hitung harga, dan order otomatis. Barangnya langsung balik di response, saldo kamu kepotong di saat yang sama. Nggak perlu nunggu callback.
Sekali request, beres
POST /order langsung ngembaliin isi produk. Nggak ada polling, nggak ada callback wajib.
Retry aman
Tiap order pakai ref dari sistem kamu. Kirim ulang ref yang sama, nggak bakal kebeli dua kali.
Prabayar, Rupiah
Top up saldo lewat QRIS di bot. Semua harga IDR, harga grosir otomatis kepake sesuai qty.
Webhook + HMAC
Update status dikirim ke server kamu, ditandatangani HMAC-SHA256.
Quick start
- Tekan tombol 🔌 API di bot Premilo (atau ketik
/api). - Top up saldo minimal Rp 20.000 lewat bot (QRIS). Begitu saldonya cukup, akses API kamu aktif otomatis dan kamu dapet notif. Saldo ini yang dipakai buat order.
- Tekan Buat Kunci. API key muncul sekali di chat kamu, dan pesannya kehapus sendiri 15 menit kemudian. Simpan di env server.
- Hit
GET /v1/productsbuat ambilproduct_id, terusPOST /v1/order. Mau coba dulu tanpa saldo? Pakai Sandbox.
Auth
Semua request (kecuali /v1/health) wajib bawa API key di header X-API-Key. Mau pakai Authorization: Bearer <key> juga bisa.
curl https://api.premilo.id/v1/me \ -H "X-API-Key: prm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- Format key:
prm_live_+ 32 karakter random. - Server nggak nyimpen key mentah, cuma hash-nya. Key hilang? Generate baru lewat tombol 🔌 API, key lama langsung mati.
- Ganti key bisa dari bot (🔌 API, Ganti Kunci) atau POST /v1/keys/rotate. Tiap kali diganti, kamu dapet notif di Telegram.
Jangan taruh API key di frontend atau aplikasi yang dipegang pembeli. Siapa pun yang pegang key bisa belanja pakai saldo kamu. Panggil API cuma dari server atau bot kamu.
Rate Limit
Default 60 request/menit per key (bisa beda per akun, cek rate_limit_per_minute di /me). Tiap response yang lolos auth bawa header ini:
| Header | Isinya |
|---|---|
X-RateLimit-Limit | Limit per menit buat key ini |
X-RateLimit-Remaining | Sisa jatah di window 60 detik yang lagi jalan |
X-RateLimit-Reset | Berapa detik lagi jatahnya mulai pulih |
Retry-After | Cuma muncul di response 429: tunggu sekian detik |
X-Request-Id | ID request. Sertain kalau lapor masalah ke owner |
Limit unit harian
Beberapa produk punya limit unit per hari per akun (lihat daily_limit_applies di /products dan daily_units_remaining di /me). Unit dari stock_ready nggak dihitung. Reset tiap 00.00 WIB. Kalau mentok, order ditolak DAILY_LIMIT_REACHED dan saldo aman.
Limit Order
Semua batas di bawah berlaku per akun API. Yang bertanda "bisa diubah" bisa dinaikkan owner kalau volume kamu memang besar.
| Batas | Nilai | Catatan |
|---|---|---|
| Qty per order | 1-1000 | Butuh lebih? Pecah jadi beberapa order dengan ref berbeda |
| Rate limit per key | 60 request/menit | Bisa diubah. Cek rate_limit_per_minute di /me |
| Rate limit per IP | 120 request/menit | Dihitung sebelum auth |
| Key salah dari satu IP | 20x/menit | Lewat dari itu, IP tersebut kena 429 sementara |
| Unit harian | 50 unit/hari (akun baru) | Hanya produk dengan daily_limit_applies: true; stock_ready nggak dihitung. Reset 00.00 WIB. Volume kamu besar? Minta owner naikin |
| Saldo | Prabayar | Order ditolak kalau saldo kurang. Nggak ada utang, nggak ada tagihan belakangan |
| Stok | Sesuai stock | Dicek ulang tepat sebelum order diproses |
| Body request | Maks 8 KB | Lebih dari itu dijawab 413 |
| Durasi order besar | Sampai ±2 menit | Set timeout client minimal 150 detik |
| Webhook | Maks 3 per akun | Test webhook maks 10x/menit |
| Riwayat | Maks 100 per halaman | Pakai page untuk halaman berikutnya |
| Aktivasi API | Saldo min Rp 20.000 | Sekali aktif, tetap aktif walau saldo turun |
Idempotency (ref)
Tiap POST /v1/order wajib bawa ref: ID order dari sistem kamu sendiri (6-64 karakter: huruf, angka, . _ : -). Unik per akun.
| Kirim ulang ref yang sama, order sebelumnya… | Hasilnya |
|---|---|
success | Dapet response lama apa adanya (items ikut) + replay: true. Saldo nggak kepotong lagi. |
failed | Order di-retry pakai ref yang sama. Aman: order failed nggak pernah motong saldo, atau saldonya udah dibalikin. |
processing | Ditolak 409 ORDER_PROCESSING. Tunggu bentar, cek GET /order/{ref}. |
needs_review | Dapet order lama. Jangan bikin ref baru buat barang yang sama, owner lagi ngecek. |
| produk atau qty-nya beda | Ditolak 409 REF_CONFLICT. Satu ref = satu isi order. |
Timeout atau koneksi putus? Jangan langsung bikin ref baru. Retry pakai ref yang sama, atau cek GET /order/{ref}. Order gede bisa makan sampai ±2 menit, jadi set timeout client minimal 150 detik.
Status Order
| Status | Artinya | Saldo |
|---|---|---|
success | Barang kekirim, ada di items | Kepotong sebesar total_charged |
failed | Order ditolak atau gagal, cek error.code. Boleh retry pakai ref yang sama | Nggak kepotong, atau udah dibalikin (balance_refunded: true) |
processing | Masih diproses | Belum pasti |
needs_review | Berhenti di tengah jalan, lagi dicek manual sama owner. Nanti berubah jadi success atau failed (webhook order.refunded / order.failed) | Belum pasti |
Kalau barang yang kekirim kurang dari yang dipesan, status tetap success: qty_delivered lebih kecil dari qty_ordered, dan selisihnya langsung balik ke saldo (refunded_amount).
Response & Error
Semua response JSON. Kalau sukses: success: true + isinya di data. Kalau gagal: HTTP status yang sesuai + objek error.
{
"success": false,
"error": { "code": "INSUFFICIENT_BALANCE", "message": "Saldo tidak mencukupi. Top up saldo lewat bot" },
"data": { "order_id": "API-3F9A1C0B7E", "ref": "INV-1001", "status": "failed", "required": 130000, "balance": 25000, "retryable": true },
"request_id": "9c1e4b7a2f0d8e61"
}| HTTP | Kode | Artinya & harus ngapain |
|---|---|---|
| 400 | INVALID_JSON | Body bukan JSON object yang valid |
| 400 | INVALID_REF | Ref kosong atau formatnya salah |
| 400 | INVALID_QTY | qty harus integer 1-1000 |
| 400 | INVALID_PARAMETER | Query atau path parameter nggak valid |
| 400 | INVALID_WEBHOOK_URL | URL webhook ditolak, alasannya ada di message |
| 401 | UNAUTHORIZED | Key salah, udah dicabut, atau akses API belum di-approve |
| 402 | INSUFFICIENT_BALANCE | Saldo kurang. Top up, terus retry pakai ref yang sama |
| 404 | PRODUCT_NOT_FOUND | ID produk nggak ada atau nggak dibuka buat akun kamu |
| 404 | ORDER_NOT_FOUND | Ref atau order_id nggak dikenal |
| 404 | WEBHOOK_NOT_FOUND | ID webhook nggak dikenal |
| 404 | NOT_FOUND | Endpoint-nya nggak ada |
| 405 | METHOD_NOT_ALLOWED | HTTP method salah, cek header Allow |
| 409 | OUT_OF_STOCK | Stok nggak cukup. Saldo aman |
| 409 | PRODUCT_BUSY | Produk lagi proses order lain. Retry pakai ref yang sama beberapa detik lagi |
| 409 | REF_CONFLICT | Ref udah dipakai buat produk atau qty lain |
| 409 | ORDER_PROCESSING | Order dengan ref ini masih diproses |
| 409 | WEBHOOK_LIMIT | Udah 3 webhook, hapus salah satu dulu |
| 413 | PAYLOAD_TOO_LARGE | Body lebih dari 8 KB |
| 415 | UNSUPPORTED_MEDIA_TYPE | POST wajib Content-Type: application/json |
| 429 | RATE_LIMITED | Kebanyakan request. Tunggu sesuai Retry-After |
| 429 | DAILY_LIMIT_REACHED | Limit unit harian produk ini udah mentok |
| 503 | PRODUCT_UNAVAILABLE | Produk lagi nggak bisa dipesan. Saldo nggak kepotong atau udah dibalikin |
| 503 | SERVICE_UNAVAILABLE | Lagi ada gangguan di sisi kami. Coba lagi bentar |
Endpoints
/v1/meInfo akun: saldo, rate limit, dan limit unit harian. allowed_products isinya null kalau semua produk kebuka buat kamu.
{
"success": true,
"data": {
"name": "TokoKamu",
"telegram_id": "5670411211",
"balance": 250000,
"currency": "IDR",
"key_prefix": "prm_live_AbCdEfGhIj",
"key_created_at": "2026-11-02T03:15:00.000Z",
"rate_limit_per_minute": 60,
"daily_unit_limit": 50,
"daily_units_used": 12,
"daily_units_remaining": 38,
"allowed_products": null,
"webhooks": 1
}
}/v1/productsKatalog lengkap: harga, harga grosir bertingkat, dan stok. Data stok di-refresh sekitar tiap 20 detik, jadi aman di-cache sebentar di sisi kamu.
| Field | Isinya |
|---|---|
id | Dipakai sebagai product_id waktu order |
price | Harga satuan normal (IDR) |
wholesale_tiers | Harga grosir: mulai min_qty item, harga per item jadi price. Dihitung dari qty dalam satu order |
stock | Total yang bisa dipesan sekarang |
stock_ready | Stok siap kirim, nggak kena limit harian |
daily_limit_applies | true kalau produk ini kena limit unit harian |
max_qty | Maksimal qty per order saat ini |
{
"success": true,
"data": {
"currency": "IDR",
"updated_at": "2026-11-02T03:20:11.000Z",
"products": [
{
"id": "contoh_varian",
"product": "NAMA PRODUK",
"name": "Nama Varian",
"price": 14000,
"wholesale_tiers": [
{ "min_qty": 10, "price": 13000 },
{ "min_qty": 20, "price": 12000 }
],
"stock": 1450,
"stock_ready": 0,
"daily_limit_applies": true,
"max_qty": 1000
}
]
}
}/v1/quote?product_id=…&qty=…Hitung harga pasti buat qty tertentu dan cek order-nya bakal lolos atau nggak, tanpa beli apa-apa.
| Parameter | Wajib | Keterangan |
|---|---|---|
product_id | Ya | ID dari /products |
qty | Ya | 1-1000 |
{
"success": true,
"data": {
"product_id": "contoh_varian",
"qty": 25,
"unit_price": 12000,
"total": 300000,
"currency": "IDR",
"wholesale_tier_min_qty": 20,
"stock": 1450,
"stock_enough": true,
"balance": 250000,
"balance_enough": false,
"daily_units_remaining": 38,
"can_order": false,
"reasons": ["INSUFFICIENT_BALANCE"],
"note": "Harga & stok dipastikan ulang saat POST /v1/order"
}
}/v1/orderBeli produk. Saldo kepotong dan isi produk balik di response yang sama.
| Field body | Tipe | Keterangan |
|---|---|---|
ref | string | Wajib. ID order dari sistem kamu, lihat Idempotency |
product_id | string | Wajib. ID dari /products |
qty | integer | Wajib. 1-1000. Harga grosir otomatis kepake sesuai qty |
{
"success": true,
"data": {
"order_id": "API-3F9A1C0B7E",
"ref": "INV-1001",
"status": "success",
"product_id": "contoh_varian",
"product": "NAMA PRODUK",
"name": "Nama Varian",
"qty_ordered": 25,
"qty_delivered": 25,
"unit_price": 12000,
"total_charged": 300000,
"refunded_amount": 0,
"attempts": 1,
"created_at": "2026-11-02T03:21:40.120Z",
"completed_at": "2026-11-02T03:21:43.870Z",
"items": ["isi produk 1", "isi produk 2", "..."],
"balance_after": 150000,
"terms": "Syarat & ketentuan produk (kalau ada)"
}
}Order yang berhenti di tengah jalan dijawab 202 dengan status needs_review. Order failed pakai HTTP status error (lihat tabel Error) dan tetap bawa data order-nya.
/v1/order/{ref}Detail satu order pakai ref (atau order_id). Order success bawa items. Aman dipanggil berkali-kali.
{
"success": true,
"data": {
"order_id": "API-3F9A1C0B7E",
"ref": "INV-1001",
"status": "success",
"qty_delivered": 25,
"total_charged": 300000,
"items": ["isi produk 1", "isi produk 2", "..."]
}
}/v1/ordersRiwayat order, yang terbaru di atas. Isinya ringkasan aja: items nggak ikut, ambil lewat GET /order/{ref}.
| Parameter | Default | Keterangan |
|---|---|---|
status | semua | success, failed, processing, atau needs_review |
page | 1 | 1-1000 |
limit | 20 | 1-100 |
{
"success": true,
"data": {
"orders": [
{ "order_id": "API-3F9A1C0B7E", "ref": "INV-1001", "status": "success", "qty_delivered": 25, "total_charged": 300000 }
],
"page": 1,
"limit": 20,
"total": 1,
"has_more": false
}
}/v1/statsRingkasan pemakaian hari ini (sejak 00.00 WIB) dan 7 hari terakhir.
{
"success": true,
"data": {
"currency": "IDR",
"timezone": "Asia/Jakarta",
"today": { "orders_success": 12, "orders_failed": 1, "orders_needs_review": 0, "units_delivered": 140, "total_spent": 1820000 },
"last_7_days": { "orders_success": 60, "orders_failed": 3, "orders_needs_review": 0, "units_delivered": 700, "total_spent": 9100000 }
}
}Angka di atas cuma contoh.
/v1/keys/rotateBikin API key baru. Key lama langsung mati, dan key baru cuma muncul di response ini. Nggak perlu body.
{
"success": true,
"data": {
"api_key": "prm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"key_prefix": "prm_live_xxxxxxxxxxx",
"created_at": "2026-11-02T03:30:00.000Z",
"message": "Simpan kunci ini sekarang. Kunci tidak akan ditampilkan lagi, dan kunci lama langsung tidak berlaku."
}
}/v1/healthCek API hidup atau nggak. Satu-satunya endpoint yang nggak butuh API key.
{ "success": true, "data": { "status": "ok" } }Webhooks
Order biasa udah beres di response POST /order, jadi webhook sifatnya pelengkap. Enak buat nyatet semua perubahan status di satu tempat, terutama order needs_review yang diberesin owner belakangan.
| Event | Kapan dikirim |
|---|---|
order.success | Order berhasil |
order.failed | Order gagal (saldo nggak kepotong atau udah dibalikin) |
order.needs_review | Order berhenti di tengah jalan, nunggu dicek owner |
order.refunded | Order needs_review dibatalin owner dan saldonya dibalikin |
ping | Cuma dari POST /webhooks/{id}/test |
Payload
Dikirim sebagai POST JSON. Isinya ringkasan order tanpa isi produk. Ambil items lewat GET /order/{ref}, biar kalau URL atau log server kamu bocor, barangnya nggak ikut bocor.
{
"id": "evt_5b0c2f1e9a7d4c3b8e6f1a2d",
"type": "order.success",
"created_at": "2026-11-02T03:21:44.010Z",
"data": {
"order_id": "API-3F9A1C0B7E",
"ref": "INV-1001",
"status": "success",
"qty_delivered": 25,
"total_charged": 300000
}
}| Header | Isinya |
|---|---|
X-Premilo-Event | Jenis event, sama dengan type |
X-Premilo-Delivery | ID pengiriman (beda tiap kirim) |
X-Premilo-Signature | t=<unix detik>,v1=<hex HMAC-SHA256> |
User-Agent | Premilo-Webhook/1.0 |
Syarat URL
- Wajib
https://di port 443, pakai domain (bukan IP), dan resolve ke IP publik. - Balas 2xx dalam 5 detik. Redirect nggak diikutin. Proses yang berat kerjain setelah bales.
- Gagal? Di-retry 5x: 30 detik, 2 menit, 10 menit, 30 menit, terus 2 jam. 20 gagal beruntun = webhook dimatiin, dan kamu dapet notif di Telegram.
- Event yang sama bisa dateng lebih dari sekali. Dedupe pakai
idevent.
Verifikasi Signature
Signature = HMAC-SHA256 pakai secret webhook (dikasih sekali pas daftar) atas string <t>.<raw body>. Pakai raw body persis kayak yang diterima, sebelum di-parse. Tolak kalau t lebih tua dari 5 menit biar nggak bisa di-replay.
const crypto = require('crypto');
// rawBody: body PERSIS seperti diterima (mis. pakai express.raw())
function verifikasiPremilo(rawBody, headerSignature, secret) {
const bagian = Object.fromEntries(String(headerSignature).split(',').map(s => s.split('=')));
const t = Number(bagian.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const harap = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(harap), b = Buffer.from(String(bagian.v1 || ''));
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
import hmac, hashlib, time
def verifikasi_premilo(raw_body: bytes, header_signature: str, secret: str) -> bool:
bagian = dict(p.split("=", 1) for p in header_signature.split(","))
t = int(bagian.get("t", "0"))
if abs(time.time() - t) > 300:
return False
harap = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(harap, bagian.get("v1", ""))
<?php
function verifikasi_premilo(string $rawBody, string $header, string $secret): bool {
parse_str(str_replace(',', '&', $header), $bagian);
$t = (int)($bagian['t'] ?? 0);
if (abs(time() - $t) > 300) return false;
$harap = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
return hash_equals($harap, $bagian['v1'] ?? '');
}
// $rawBody = file_get_contents('php://input');
/v1/webhooksDaftarin URL webhook. Maksimal 3 per akun. events opsional: kalau nggak diisi, semua event dikirim. secret cuma muncul di response ini.
{
"success": true,
"data": {
"id": "wh_8d2c4f6a1b3e5d7f9a0c2e4b",
"url": "https://tokokamu.id/webhook/premilo",
"events": ["order.success", "order.failed", "order.refunded"],
"active": true,
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"message": "Simpan secret ini untuk memverifikasi tanda tangan. Secret tidak akan ditampilkan lagi."
}
}/v1/webhooksList webhook plus status pengiriman terakhirnya. Secret nggak ikut ditampilin.
{
"success": true,
"data": {
"webhooks": [
{
"id": "wh_8d2c4f6a1b3e5d7f9a0c2e4b",
"url": "https://tokokamu.id/webhook/premilo",
"events": ["order.success", "order.failed", "order.refunded"],
"active": true,
"consecutive_failures": 0,
"last_success_at": "2026-11-02T03:21:44.300Z",
"last_failure_at": null,
"last_error": null
}
]
}
}/v1/webhooks/{id}/testKirim event ping sekarang juga dan lihat hasilnya langsung. Maksimal 10x per menit.
{
"success": true,
"data": { "id": "wh_8d2c4f6a1b3e5d7f9a0c2e4b", "delivered": true, "status_code": 200, "duration_ms": 184, "error": null }
}/v1/webhooks/{id}Hapus webhook. Pengiriman yang masih ngantre buat webhook ini ikut dibatalin.
{ "success": true, "data": { "id": "wh_8d2c4f6a1b3e5d7f9a0c2e4b", "deleted": true } }Prompt untuk AI
Pakai ChatGPT, Claude, Gemini, Cursor, atau Codex buat ngoding integrasinya? Copy prompt ini, ganti bagian dalam kurung siku, lalu tempel ke AI kamu. Prompt-nya udah berisi aturan penting (idempotency, retry, verifikasi webhook) biar kode yang dihasilkan nggak bikin order dobel atau bocorin key.
Kamu adalah senior backend engineer. Bantu saya integrasi Premilo Reseller API ke [BOT TELEGRAM / WEBSITE / PANEL] saya yang ditulis dengan [BAHASA & FRAMEWORK, mis. Node.js + Telegraf / PHP Laravel / Python FastAPI].
Spesifikasi lengkap (OpenAPI 3.1): https://api.premilo.id/openapi.json
Dokumentasi: https://api.premilo.id/docs
RINGKASAN API
- Base URL: https://api.premilo.id/v1
- Auth: header "X-API-Key: <key>". Baca key dari environment variable PREMILO_API_KEY. JANGAN taruh key di kode frontend/klien, log, atau repo.
- Format response: sukses {"success": true, "data": ...}; gagal {"success": false, "error": {"code", "message"}, "data"?, "request_id"}.
- GET /products: katalog (id, product, name, price, wholesale_tiers[{min_qty, price}], stock, stock_ready, max_qty). Cache 20-60 detik.
- GET /quote?product_id=&qty=: unit_price, total, can_order, reasons[] (tanpa membeli).
- POST /order body {"ref", "product_id", "qty"} (qty 1-1000): data.items (isi produk), total_charged, balance_after.
- ref = ID order unik dari sistem saya, 6-64 karakter [A-Za-z0-9._:-]. Kirim ulang ref yang SAMA tidak pernah membeli dua kali (response lama + "replay": true). Order failed boleh di-retry dengan ref yang sama.
- status: success | failed | processing | needs_review. needs_review (HTTP 202) = sedang dicek manual, JANGAN buat ref baru untuk barang yang sama.
- GET /order/{ref}: detail order + items. Dipakai saat timeout / koneksi putus.
- GET /me (saldo & limit), GET /orders (riwayat, tanpa items), GET /stats.
- Rate limit: 60 request/menit per key. Header X-RateLimit-Limit / -Remaining / -Reset, dan Retry-After saat 429.
ATURAN IMPLEMENTASI (WAJIB)
1. Timeout HTTP client untuk POST /order minimal 150 detik. Saat timeout atau network error: JANGAN buat ref baru. Panggil GET /order/{ref}, atau retry dengan ref yang sama.
2. Retry otomatis maksimal 3x (backoff 3 detik, hormati Retry-After) HANYA untuk: PRODUCT_BUSY, RATE_LIMITED, SERVICE_UNAVAILABLE, dan network error. Error lain langsung dilaporkan.
3. Pemetaan error ke pesan user: INSUFFICIENT_BALANCE = saldo kurang, minta top up; OUT_OF_STOCK / PRODUCT_UNAVAILABLE = stok habis; DAILY_LIMIT_REACHED = coba lagi besok (reset 00.00 WIB); INVALID_* = bug di request, perbaiki kode.
4. Simpan order_id, ref, status, total_charged, dan items ke database saya SEBELUM dikirim ke pembeli.
5. Kalau pakai webhook: verifikasi header X-Premilo-Signature (format "t=<unix>,v1=<hex>") = HMAC-SHA256(secret, "<t>.<raw body>") memakai raw body sebelum di-parse, tolak kalau t lebih tua dari 300 detik, bandingkan secara timing-safe, balas 2xx dalam 5 detik, dan dedupe berdasarkan "id" event. Payload webhook tidak memuat items; ambil lewat GET /order/{ref}.
6. Jangan pernah mencetak API key atau isi items ke log.
TUGAS
Buatkan [mis. modul klien API + fungsi beli(ref, productId, qty) + handler webhook + perintah /beli di bot saya], lengkap dengan error handling sesuai aturan di atas, struktur folder, dan contoh pemakaian. Tulis kode yang siap produksi.OpenAPI 3.1
Spesifikasi mesin-baca untuk semua endpoint, schema request/response, kode error, dan webhook. Bisa di-import ke Postman, Insomnia, atau dipakai generator SDK (openapi-generator, openapi-typescript).
curl -o premilo-openapi.json https://api.premilo.id/openapi.json # contoh: generate tipe TypeScript npx openapi-typescript https://api.premilo.id/openapi.json -o premilo.d.ts
Contoh Flow Lengkap
Pola yang disaranin buat bot atau toko: quote dulu, order pakai ref dari sistem kamu, dan kalau koneksi bermasalah, cek status pakai ref yang sama.
const API = 'https://api.premilo.id/v1';
const HEADER = { 'X-API-Key': process.env.PREMILO_API_KEY, 'Content-Type': 'application/json' };
async function panggil(path, opsi = {}) {
const res = await fetch(API + path, { ...opsi, headers: HEADER, signal: AbortSignal.timeout(150000) });
return { status: res.status, isi: await res.json() };
}
async function beli(refOrder, productId, qty) {
const q = await panggil(`/quote?product_id=${encodeURIComponent(productId)}&qty=${qty}`);
if (!q.isi.success || !q.isi.data.can_order) return { ok: false, alasan: q.isi.data?.reasons || q.isi.error };
for (let percobaan = 1; percobaan <= 3; percobaan++) {
try {
const o = await panggil('/order', { method: 'POST', body: JSON.stringify({ ref: refOrder, product_id: productId, qty }) });
const d = o.isi.data;
if (d?.status === 'success') return { ok: true, items: d.items, total: d.total_charged };
if (d?.status === 'needs_review') return { ok: false, alasan: 'Lagi dicek owner, tunggu webhook' };
if (o.isi.error?.code === 'PRODUCT_BUSY') { await new Promise(r => setTimeout(r, 3000)); continue; }
return { ok: false, alasan: o.isi.error };
} catch (e) {
// Timeout / koneksi putus: JANGAN bikin ref baru. Cek status ref yang sama.
const s = await panggil(`/order/${encodeURIComponent(refOrder)}`).catch(() => null);
if (s?.isi.data?.status === 'success') return { ok: true, items: s.isi.data.items };
if (s?.isi.data?.status === 'processing') await new Promise(r => setTimeout(r, 5000));
}
}
return { ok: false, alasan: 'Belum pasti, cek lagi nanti pakai ref yang sama' };
}Playground
Coba semua endpoint langsung dari browser. Sandbox jalan sepenuhnya di browser kamu: saldo, produk, dan order-nya palsu, aturan & kode error-nya sama persis dengan API asli. Live manggil api.premilo.id pakai API key kamu.
API Console
Mode Live pakai akun dan saldo beneran. POST /order beneran beli dan motong saldo. API key cuma disimpan di memori tab ini.
Kalkulator Grosir
Hitung total pakai harga grosir bertingkat. Mode Sandbox langsung pakai produk contoh; mode Live pakai katalog asli dengan API key kamu (pilih modenya di Playground).
Best Practice
- Simpan API key di env atau secret manager server, jangan di-commit ke repo.
- Pakai ref dari ID order di sistem kamu, biar retry otomatis nggak pernah beli dua kali.
- Set timeout client minimal 150 detik buat POST /order. Kalau tetep timeout, cek GET /order/{ref}.
- Cache /products 20-60 detik di sisi kamu, nggak perlu dipanggil tiap order.
- Kena
PRODUCT_BUSYatauRATE_LIMITED? Tunggu bentar, terus retry pakai ref yang sama. - Curiga key bocor? Langsung ganti lewat
/apidi bot. Key lama mati saat itu juga.
Changelog
v1.0.0
- Rilis pertama: katalog, quote, order idempotent per
ref, riwayat, statistik, rotasi key. - Webhook bertanda tangan HMAC-SHA256 dengan retry otomatis.
- Spesifikasi OpenAPI 3.1, Playground Sandbox/Live, dan prompt integrasi untuk AI.
Versi API ada di path (/v1). Perubahan yang bikin kode lama rusak cuma bakal dirilis di versi baru (/v2), bukan diam-diam di /v1.
X-Request-Id dari response yang bermasalah.