Skip to content

Console API Publik (Tanpa Login)

Semua endpoint di bawah tidak butuh token. Base URL:

https://console.balitechsolution.com

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

Terminal window
curl -X POST https://console.balitechsolution.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"rahasia"}'

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

Terminal window
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)

Terminal window
curl https://console.balitechsolution.com/api/villas/1

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

Terminal window
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.

Terminal window
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.

Terminal window
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).

Terminal window
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).

Terminal window
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.

Terminal window
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.

Terminal window
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).

Terminal window
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.

Terminal window
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 bertoken
const 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>