Console API Publik (Tanpa Login)
Semua endpoint di bawah tidak butuh token. Base URL:
https://console.balitechsolution.comCORS terbuka (Access-Control-Allow-Origin: *, kecuali /api/pricing-plans yang
mengizinkan origin site saja), jadi bisa di-fetch langsung dari browser.
Method lain selain yang tercantum dijawab 405. OPTIONS selalu dijawab 204
(preflight).
Yang butuh token (
Authorization: Bearer blt_.../X-API-Token) TIDAK dibahas di sini:GET /api/auth/me,POST /api/auth/logout,GET /api/users.
1. POST /api/auth/login — terbitkan token
Titik masuk untuk dapat token. Tanpa 2FA global: email + password langsung
terbit token (berlaku 30 hari). Kalau akun 2FA masih mengirim code/totp/otp
yang salah → ditolak 2fa_invalid. Hanya superadmin atau akun yang terikat
company_id yang dapat token, selain itu 403 company_required.
curl -X POST https://console.balitechsolution.com/api/auth/login \ -H "Content-Type: application/json" \Body JSON (Content-Type: application/json) atau form — kunci: email, password,
opsional code/totp/otp.
Sukses 200:
{ "ok": true, "token": "blt_...", "token_type": "Bearer", "expires_at": "2026-11-13T00:00:00Z", "user": {"id": 1, "name": "...", "email": "...", "role": "user", "company_id": 7, "company": "...", "is_superadmin": false}}Error: 400 email/password kosong · 401 salah · 403 IP diblokir /
company_required · 429 terlalu banyak percobaan (cek header Retry-After).
2. GET /api/villas — daftar villa (publik)
Katalog publik. Tanpa token bisa diakses; kalau kirim token valid, scope
mengikuti token (akun perusahaan dikunci ke villanya, ?company_id= diabaikan;
superadmin boleh filter). Token salah/kedaluwarsa → 401 (bukan fallback publik).
curl "https://console.balitechsolution.com/api/villas?q=benoa&company_id=7&category=villa"Query opsional: q (cari nama), company_id (ID perusahaan), category
(villa|hotel|tanah|ruko|apartemen|lainnya, selain itu diabaikan).
Sukses 200:
{"ok": true, "data": [ {"id": 1, "name": "Villa A", "company_id": 7, "company": "...", "company_phone": "...", "company_email": "...", "company_logo": "https://...", "category": "villa", "location": "Tanjung Benoa", "maps_url": "...", "cover_url": "https://.../uploads/villas/....jpg", "total_rooms": 5, "rooms": [{"floor": "2nd Floor", "label": "...", "size": "...", "qty": 1, "description": "..."}], "files": [{"type": "photo", "url": "https://...", "caption": "..."}]}], "total": 1}3. GET /api/villas/{id} — detail villa (publik)
curl https://console.balitechsolution.com/api/villas/1Sukses 200: {"ok": true, "data": {...sama seperti item di atas...}}.
Tidak ada / ID milik tenant lain (saat pakai token perusahaan) → 404.
4. GET /api/companies — daftar perusahaan (publik)
Hanya field publik: nama, kontak, logo absolut, jumlah villa.
curl https://console.balitechsolution.com/api/companies{"ok": true, "data": [ {"id": 7, "name": "...", "phone": "...", "email": "...", "logo": "https://.../uploads/...", "villa_count": 3}], "total": 1}5. GET /api/pricing-plans — daftar plan aktif (publik)
Untuk halaman pricing Astro.
curl https://console.balitechsolution.com/api/pricing-plans{"data": [ {"id": 1, "name": "...", "slug": "...", "price": 17000, "price_unit": "monthly", "billing_period": "monthly", "max_token": 8000, "rate_limit": 30, "requests_per_day": 1000, "requests_per_month": 0, "is_unlimited_request": true, "mcp_enabled": false, "badge": "", "is_best_value": false, "total_purchases": 12, "features": ["..."]}]}Catatan: tidak ada wrapper ok; harga plan beli-token-custom selalu
di-generate dari tier server (kolom DB diabaikan).
6. GET /api/custom-token-price — hitung harga token custom (publik)
juta wajib, kelipatan 10, minimal 10. Di atas tier terbesar pakai tarif linear.
curl "https://console.balitechsolution.com/api/custom-token-price?juta=100"{"juta": 100, "tokens": 100000000, "price": 17000, "period": "14 hari", "price_per_million": 170}Error 400: {"error": "minimal 10 juta token"} /
{"error": "harus kelipatan 10 juta"}.
7. GET /api/public-stats — angka publik website (publik)
Dihitung dari DB + katalog proxy, di-cache 5 menit
(Cache-Control: public, max-age=300).
curl https://console.balitechsolution.com/api/public-stats{"models_active": 63, "models_display": "60+", "providers": 5, "users_registered": 636, "users_display": "600+", "daily_grant_tokens": 2000000, "daily_grant_label": "2M", "custom_token": {"min_price": 2900, "min_price_label": "Rp 2.900", "min_juta": 10, "step_juta": 10, "period_days": 14, "tiers": [...], "summary": "10 juta → Rp 2.900 · ...", "summary_short": "10M Rp 2,9rb · ..."}, "daily_plan_min_price": 10900, "daily_plan_min_price_label": "Rp 10.900", "updated_at": "2026-10-14T00:00:00Z"}DB/proxy down → 503 {"error": "stats unavailable"}.
8. GET /api/vps-plans — plan VPS + OS image (publik)
Harga real-time dari Biznetgio. Kalau token Biznetgio bermasalah → fallback
{"ok": false, "plans": [], "images": []} (frontend pakai data statis).
curl https://console.balitechsolution.com/api/vps-plans{"ok": true, "plans": [...], "images": [...]}9. GET /api/trend — tren pemakaian 7 hari (publik)
Untuk grafik frontend website. Tanpa auth.
curl https://console.balitechsolution.com/api/trend{"trend": [...]}DB error → {"error": "..."} (status tetap 200).
10. GET /api/testimonials — testimoni approved (publik)
Maks 9 baris, hanya field publik (tanpa email/user_id — UU PDP).
Cache-Control: public, max-age=60.
curl https://console.balitechsolution.com/api/testimonials{"data": [ {"id": 1, "name": "...", "role": "...", "content": "...", "rating": 5, "avatar_url": "...", "created_at": "2026-10-01T00:00:00Z"}]}11. GET /api/fuel-prices — pantauan BBM Bali (publik)
Harga acuan + status SPBU contoh per kab/kota + laporan warga.
Cache-Control: public, max-age=300. Query opsional ?region=badung
(slug: denpasar, badung, gianyar, tabanan, klungkung, bangli, karangasem,
buleleng, jembrana).
curl https://console.balitechsolution.com/api/fuel-prices?region=badung{"ok": true, "updated_at": "2026-10-15T00:00:00Z", "source": "Harga acuan nasional Pertamina; status SPBU contoh + laporan warga.", "disclaimer": "Harga dapat berbeda tipis antar SPBU. Status ketersediaan bersifat pantauan komunitas, bukan data resmi Pertamina.", "prices": [{"type": "pertalite", "name": "Pertalite", "category": "subsidi", "price": 10000, "unit": "per liter"}], "regions": [{"slug": "badung", "name": "Kab. Badung"}], "stations": [{"id": "spbu-badung-1", "name": "SPBU Sunset Road", "code": "54.803.21", "region": "badung", "address": "Jl. Sunset Road, Kuta", "fuels": [{"type": "pertalite", "status": "tersedia", "updated_at": "..."}]}], "reports": []}Status BBM: tersedia | menipis | kosong.
12. GET / POST /api/fuel-reports — laporan stok warga (publik)
GET mengembalikan laporan terbaru dulu (maks 50). POST menerima JSON
(station, region, fuel_type wajib; status salah satu dari tiga di atas;
note maks 280 karakter) dan menjawab 201.
curl -X POST https://console.balitechsolution.com/api/fuel-reports \ -H "Content-Type: application/json" \ -d '{"station":"SPBU Sunset Road","region":"badung","fuel_type":"pertalite","status":"menipis","note":"antre 10 motor"}'Contoh fetch dari browser
const BASE = "https://console.balitechsolution.com";
// Katalog + detail (tanpa token)const list = await (await fetch(`${BASE}/api/villas?company_id=7`)).json();const items = list.data ?? list;
const detail = await (await fetch(`${BASE}/api/villas/1`)).json();
// Login lalu pakai token untuk endpoint bertokenconst l = await (await fetch(`${BASE}/api/auth/login`, { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({email, password}),})).json();if (!l.ok) throw new Error(l.error);const token = l.token; // simpan, kirim sebagai Authorization: Bearer <token>