Premilo API Reseller v1
Cek status… OpenAPI Playground

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.

Base URL https://api.premilo.id/v1

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

  1. Tekan tombol 🔌 API di bot Premilo (atau ketik /api).
  2. 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.
  3. Tekan Buat Kunci. API key muncul sekali di chat kamu, dan pesannya kehapus sendiri 15 menit kemudian. Simpan di env server.
  4. Hit GET /v1/products buat ambil product_id, terus POST /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:

HeaderIsinya
X-RateLimit-LimitLimit per menit buat key ini
X-RateLimit-RemainingSisa jatah di window 60 detik yang lagi jalan
X-RateLimit-ResetBerapa detik lagi jatahnya mulai pulih
Retry-AfterCuma muncul di response 429: tunggu sekian detik
X-Request-IdID 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.

BatasNilaiCatatan
Qty per order1-1000Butuh lebih? Pecah jadi beberapa order dengan ref berbeda
Rate limit per key60 request/menitBisa diubah. Cek rate_limit_per_minute di /me
Rate limit per IP120 request/menitDihitung sebelum auth
Key salah dari satu IP20x/menitLewat dari itu, IP tersebut kena 429 sementara
Unit harian50 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
SaldoPrabayarOrder ditolak kalau saldo kurang. Nggak ada utang, nggak ada tagihan belakangan
StokSesuai stockDicek ulang tepat sebelum order diproses
Body requestMaks 8 KBLebih dari itu dijawab 413
Durasi order besarSampai ±2 menitSet timeout client minimal 150 detik
WebhookMaks 3 per akunTest webhook maks 10x/menit
RiwayatMaks 100 per halamanPakai page untuk halaman berikutnya
Aktivasi APISaldo min Rp 20.000Sekali 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
successDapet response lama apa adanya (items ikut) + replay: true. Saldo nggak kepotong lagi.
failedOrder di-retry pakai ref yang sama. Aman: order failed nggak pernah motong saldo, atau saldonya udah dibalikin.
processingDitolak 409 ORDER_PROCESSING. Tunggu bentar, cek GET /order/{ref}.
needs_reviewDapet order lama. Jangan bikin ref baru buat barang yang sama, owner lagi ngecek.
produk atau qty-nya bedaDitolak 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

StatusArtinyaSaldo
successBarang kekirim, ada di itemsKepotong sebesar total_charged
failedOrder ditolak atau gagal, cek error.code. Boleh retry pakai ref yang samaNggak kepotong, atau udah dibalikin (balance_refunded: true)
processingMasih diprosesBelum pasti
needs_reviewBerhenti 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"
}
HTTPKodeArtinya & harus ngapain
400INVALID_JSONBody bukan JSON object yang valid
400INVALID_REFRef kosong atau formatnya salah
400INVALID_QTYqty harus integer 1-1000
400INVALID_PARAMETERQuery atau path parameter nggak valid
400INVALID_WEBHOOK_URLURL webhook ditolak, alasannya ada di message
401UNAUTHORIZEDKey salah, udah dicabut, atau akses API belum di-approve
402INSUFFICIENT_BALANCESaldo kurang. Top up, terus retry pakai ref yang sama
404PRODUCT_NOT_FOUNDID produk nggak ada atau nggak dibuka buat akun kamu
404ORDER_NOT_FOUNDRef atau order_id nggak dikenal
404WEBHOOK_NOT_FOUNDID webhook nggak dikenal
404NOT_FOUNDEndpoint-nya nggak ada
405METHOD_NOT_ALLOWEDHTTP method salah, cek header Allow
409OUT_OF_STOCKStok nggak cukup. Saldo aman
409PRODUCT_BUSYProduk lagi proses order lain. Retry pakai ref yang sama beberapa detik lagi
409REF_CONFLICTRef udah dipakai buat produk atau qty lain
409ORDER_PROCESSINGOrder dengan ref ini masih diproses
409WEBHOOK_LIMITUdah 3 webhook, hapus salah satu dulu
413PAYLOAD_TOO_LARGEBody lebih dari 8 KB
415UNSUPPORTED_MEDIA_TYPEPOST wajib Content-Type: application/json
429RATE_LIMITEDKebanyakan request. Tunggu sesuai Retry-After
429DAILY_LIMIT_REACHEDLimit unit harian produk ini udah mentok
503PRODUCT_UNAVAILABLEProduk lagi nggak bisa dipesan. Saldo nggak kepotong atau udah dibalikin
503SERVICE_UNAVAILABLELagi ada gangguan di sisi kami. Coba lagi bentar

Endpoints

GET/v1/me

Info 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
  }
}
GET/v1/products

Katalog lengkap: harga, harga grosir bertingkat, dan stok. Data stok di-refresh sekitar tiap 20 detik, jadi aman di-cache sebentar di sisi kamu.

FieldIsinya
idDipakai sebagai product_id waktu order
priceHarga satuan normal (IDR)
wholesale_tiersHarga grosir: mulai min_qty item, harga per item jadi price. Dihitung dari qty dalam satu order
stockTotal yang bisa dipesan sekarang
stock_readyStok siap kirim, nggak kena limit harian
daily_limit_appliestrue kalau produk ini kena limit unit harian
max_qtyMaksimal 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
      }
    ]
  }
}
GET/v1/quote?product_id=…&qty=…

Hitung harga pasti buat qty tertentu dan cek order-nya bakal lolos atau nggak, tanpa beli apa-apa.

ParameterWajibKeterangan
product_idYaID dari /products
qtyYa1-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"
  }
}
POST/v1/order

Beli produk. Saldo kepotong dan isi produk balik di response yang sama.

Field bodyTipeKeterangan
refstringWajib. ID order dari sistem kamu, lihat Idempotency
product_idstringWajib. ID dari /products
qtyintegerWajib. 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.

GET/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", "..."]
  }
}
GET/v1/orders

Riwayat order, yang terbaru di atas. Isinya ringkasan aja: items nggak ikut, ambil lewat GET /order/{ref}.

ParameterDefaultKeterangan
statussemuasuccess, failed, processing, atau needs_review
page11-1000
limit201-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
  }
}
GET/v1/stats

Ringkasan 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.

POST/v1/keys/rotate

Bikin 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."
  }
}
GET/v1/health

Cek 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.

EventKapan dikirim
order.successOrder berhasil
order.failedOrder gagal (saldo nggak kepotong atau udah dibalikin)
order.needs_reviewOrder berhenti di tengah jalan, nunggu dicek owner
order.refundedOrder needs_review dibatalin owner dan saldonya dibalikin
pingCuma 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
  }
}
HeaderIsinya
X-Premilo-EventJenis event, sama dengan type
X-Premilo-DeliveryID pengiriman (beda tiap kirim)
X-Premilo-Signaturet=<unix detik>,v1=<hex HMAC-SHA256>
User-AgentPremilo-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 id event.

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');
POST/v1/webhooks

Daftarin 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."
  }
}
GET/v1/webhooks

List 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
      }
    ]
  }
}
POST/v1/webhooks/{id}/test

Kirim 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 }
}
DELETE/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

Saldo sandboxRp 100.000
Order sandbox0
API key sandboxprm_test_…
Response bakal muncul di sini.

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_BUSY atau RATE_LIMITED? Tunggu bentar, terus retry pakai ref yang sama.
  • Curiga key bocor? Langsung ganti lewat /api di 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.

Premilo Reseller API v1. Ada masalah? Chat owner di Telegram, sertain X-Request-Id dari response yang bermasalah.

Konfirmasi