AI SaaS adalah produk AI yang dijual sebagai layanan langganan ke banyak pelanggan sekaligus. Tiap pelanggan yang membayar disebut tenant. Di pelajaran ini Asisten Toko Buku dijual ke banyak toko: Toko Buku Pelangi di Bandung, Toko Buku Senja di Yogyakarta, dan seterusnya. Bayangin pengelola gedung apartemen. Satu gedung, banyak unit. Kunci unit A gak bisa membuka unit B. Tiap unit punya meteran listrik sendiri, jadi tagihannya sesuai pemakaian. Pengelola membayar listrik ke PLN, lalu menagih penghuni dengan harga yang sudah termasuk biaya pengelolaan. Dan daya tiap unit dibatasi, supaya satu unit gak membuat sekring seluruh gedung turun.
🧪 Tebakan cepat: Toko Buku Senja gak menjual Laskar Pelangi; Toko Buku Pelangi punya 12. Pelanggan Senja bertanya "Ada Laskar Pelangi?", dan query katalognya lupa
WHERE toko_id = ?. Apa jawaban asistennya? Tebak dulu, jawabannya ada di bagian "Satu aplikasi, banyak toko: pisahkan datanya".
Satu aplikasi, banyak toko: pisahkan datanya
Multi-tenant artinya satu aplikasi dan satu database melayani banyak tenant, dan tiap baris data tahu milik siapa. Setiap tabel punya kolom toko_id, dan toko_id itu diambil dari API key yang sudah diverifikasi, bukan dari body request yang bisa diisi siapa saja.
// req.toko diisi middleware dari API key (bagian berikutnya)
const stok = await db.query(
"SELECT judul, harga, stok FROM buku WHERE toko_id = $1 AND judul ILIKE $2",
[req.toko.id, `%${judul}%`],
);
const mirip = await db.query(
"SELECT judul, sinopsis FROM buku WHERE toko_id = $1 ORDER BY embedding <=> $2 LIMIT 5",
[req.toko.id, vektorPertanyaan],
);
const kunciCache = `toko:${req.toko.id}:v${versiStok}:${normalkan(pertanyaan)}`;
- SQL dan vector search sama-sama difilter. RAG yang lupa filter mengambil sinopsis dan stok toko lain. Di pgvector, beri index B-tree di
toko_id; filter sesudah index perkiraan bisa mengembalikan baris kurang dariLIMIT(pelajaran Vector Databases). Di Pinecone, satu namespace per toko. - Cache jawaban juga per toko. Kunci dari pelajaran Cost Optimization (pertanyaan + versi stok) ditambah
toko_id. Tanpa itu, jawaban yang di-cache untuk Pelangi dibagikan ke pelanggan Senja. - Di Laravel, global scope di model
Bukumenambahkanwhere toko_idke setiap query, jadi gak ada yang lupa.
Tanpa filter, asisten menjawab "Ada! Stok Laskar Pelangi 12, Rp89.000". Itu stok Toko Pelangi: jawabannya salah untuk pelanggan Senja, dan data toko lain bocor. Dengan filter: "Laskar Pelangi belum ada di katalog Toko Buku Senja." Itu jawaban tebakannya. Tambahkan kasus ini ke eval set supaya kebocoran ketahuan sebelum rilis.
API key per toko, disimpan sebagai hash
Tiap toko memanggil API-mu dengan kuncinya sendiri. Kunci OpenAI milikmu tetap satu, di .env server, dan gak pernah dilihat toko mana pun.
import { randomBytes, createHash } from "node:crypto";
const sha256 = (s) => createHash("sha256").update(s).digest("hex");
async function buatKunci(tokoId) {
const kunci = "tb_live_" + randomBytes(24).toString("base64url"); // 32 karakter acak
await db.insert("kunci_api", { toko_id: tokoId, prefix: kunci.slice(0, 12), hash: sha256(kunci), dicabut: false });
return kunci; // ditampilkan SEKALI ke pemilik toko, lalu dilupakan
}
async function tokoDariKunci(req) {
const kunci = (req.headers.authorization ?? "").replace("Bearer ", "");
const baris = await db.first("SELECT toko_id FROM kunci_api WHERE hash = $1 AND dicabut = false", [sha256(kunci)]);
return baris?.toko_id ?? null; // null: balas 401, model gak dipanggil
}
- Database cuma menyimpan hash SHA-256. Kalau database bocor, hash gak bisa dipakai memanggil API-mu. Di Lab,
tb_live_A6NXXnvt...punya hash411fbfb7d7c3a497.... - SHA-256 cukup karena kuncinya 24 byte acak (192 bit), gak bisa ditebak satu per satu. Password beda: buatan manusia dan pendek, jadi butuh bcrypt atau Argon2.
prefix(tb_live_A6NX) ditampilkan di dasbor supaya pemilik toko tahu kunci mana yang dipakai. Kunci bocor? Buat yang baru, cabut yang lama.
Streaming lewat backend-mu, sambil mencatat meteran
Browser pelanggan gak pernah bicara langsung ke OpenAI. Server meneruskan stream seperti di pelajaran Streaming Responses, dan event terakhir, response.completed, membawa usage yang jadi angka meteran:
await bacaSSE(upstream.body, async (event) => {
if (event.type === "response.output_text.delta") res.write(`data: ${JSON.stringify({ teks: event.delta })}\n\n`);
if (event.type === "response.completed") {
const u = event.response.usage;
await db.insert("pemakaian", {
toko_id: req.toko.id, model: event.response.model, jenis: jenisPertanyaan,
input_tokens: u.input_tokens, cached_tokens: u.input_tokens_details.cached_tokens, output_tokens: u.output_tokens,
});
}
});
Biaya dihitung dari tabel pemakaian dan daftar harga. Stream yang putus sebelum response.completed gak membawa usage, jadi cocokkan meteranmu dengan dasbor usage provider tiap bulan.
Token jadi biaya, biaya jadi harga
Asumsi dari pelajaran Cost Optimization: tiap pertanyaan 1.200 token input (1.100 dari prefix yang ter-cache), output 40 token untuk stok dan 200 untuk rekomendasi:
- stok,
gpt-6-luna: (100 × $0,10 + 1.100 × $0,01 + 40 × $0,50) / 1.000.000 = $0,000041 - rekomendasi,
gpt-6.1-sol: (100 × $2 + 1.100 × $0,10 + 200 × $10) / 1.000.000 = $0,00231, 56 kali lebih mahal
Meteran bulan Oktober, dengan kurs contoh Rp16.500 per dolar:
| Toko | Stok | Rekomendasi | Biaya AI |
|---|---|---|---|
| Pelangi | 4.200 | 300 | $0,87 = Rp14.276 |
| Senja (klub buku) | 1.500 | 2.900 | $6,76 = Rp111.548 |
Paket Toko dengan harga rata, Rp149.000 untuk 5.000 pertanyaan apa saja: margin Pelangi 90,4%, Senja cuma 25,1%. Kalau satu toko memakai 5.000 rekomendasi, biayanya Rp190.575: rugi. Inilah tantangan bisnis terbesar produk AI SaaS: mengelola biaya AI per pengguna sambil menjaga margin, karena biayanya ikut naik dengan pemakaian, gak seperti SaaS biasa yang biaya per pengguna hampir tetap. (Margin = (pendapatan - biaya AI) / pendapatan; server, payment gateway, dan pajak belum dihitung.)
Jalan keluarnya pricing usage-based dengan tier dan sistem kredit, model yang paling umum untuk SaaS AI: stok = 1 kredit, rekomendasi = 50 kredit (mengikuti perbandingan biayanya). Paket Toko Rp149.000 berisi 50.000 kredit, lebihnya Rp3 per kredit. Biaya per kredit paling mahal Rp0,76, jadi tiap kredit yang terjual punya margin minimal 74%. Senja memakai 146.500 kredit: Rp149.000 + 96.500 × Rp3 = Rp438.500, margin 74,6%. Pelangi tetap Rp149.000 (19.200 kredit).
- Kuota dicek sebelum model dipanggil. Kredit habis di paket Gratis? Tolak dengan pesan "kuota bulan ini habis" dan tautan upgrade.
- Free tier dibatasi per toko, bukan per akun. Paket Gratis 1.000 kredit (biaya maksimal Rp762 per toko). Satu toko yang membuat 40 akun gratis mendapat 40.000 kredit; cegah dengan verifikasi nomor WhatsApp atau domain per toko, tanpa kelebihan pemakaian di paket Gratis, plus batas 20 pertanyaan per 10 menit per pengguna dari pelajaran AI Safety.
Observability: dasbor biaya per toko dan regresi eval
- Dasbor per toko per hari: jumlah request, token, biaya, kredit tersisa, margin. Pasang alert kalau biaya harian satu toko lebih dari 3 kali rata-rata 7 hari terakhirnya: tanda penyalahgunaan, loop agent, atau bug.
- Per jenis pertanyaan dan per model: kalau rekomendasi tiba-tiba naik, kamu tahu dari mana tagihannya.
- Regresi kualitas: 12 kasus eval (pelajaran Evaluating AI Output) plus kasus "toko lain gak bocor" jalan di CI setiap prompt, model, atau data berubah. Pantau juga tombol "Laporkan jawaban" per toko.
Hukum dan kepercayaan: dasar yang wajib
- Syarat data provider (dicek Oktober 2026): data API OpenAI gak dipakai melatih model kecuali kamu memilih ikut, log pemantauan penyalahgunaan disimpan sampai 30 hari, dan Responses API menyimpan respons 30 hari kecuali
store: false. Anthropic: data yang disimpan gak dipakai melatih model tanpa izin eksplisitmu; model Fable dan Mythos mewajibkan retensi 30 hari. Keduanya punya Data Processing Addendum (DPA) untuk pelanggan bisnis. - Syaratmu ke toko: syarat layanan dan kebijakan privasi yang menyebut subprosesor (OpenAI, Anthropic, hosting), DPA dengan tiap toko karena kamu memproses data pelanggan mereka, dan di Indonesia, UU Pelindungan Data Pribadi (UU No. 27 Tahun 2022).
- Bilang bahwa ini AI. Usage Policy Anthropic (berlaku 12 November 2026) mewajibkan chatbot yang bicara dengan pengguna luar memberi tahu bahwa mereka bicara dengan AI, minimal di awal tiap sesi atau di antarmuka. OpenAI mewajibkan pemberitahuan bahwa suara TTS buatan AI. Label widget: "Asisten AI Toko Buku Senja · jawaban bisa salah, harga pasti ada di halaman buku".
Checklist sebelum launch
- AI Landscape untuk Web Developer: yang cukup dengan kode biasa gak memanggil model; API key cuma di server.
- Machine Learning 101: semua orang di tim tahu model menebak token, jadi jawabannya bisa beda dan bisa salah.
- Prompt Engineering Fundamentals: aturan di
instructions, teks pelanggan di<pertanyaan>yang di-escape. - OpenAI API & SDK: timeout, retry,
max_output_tokens,401dan429ditangani. - Streaming Responses: stream diteruskan server, dibatalkan kalau browser pergi,
usagedicatat. - Embedding & Vector Search: buku di-embed sekali, pertanyaan dengan model yang sama.
- RAG: threshold sumber, "tidak tahu" kalau gak ada sumber, stok dari SQL.
- Function Calling & Tool Use: argumen divalidasi, jumlah putaran dibatasi.
- Building Chat UI: Stop, Coba lagi,
aria-live, teks model gak dirender sebagai HTML mentah. - LangChain / Vercel AI SDK: framework gak menggantikan validasi, izin, dan kunci di server.
- Semantic Search Implementation: hybrid BM25 + vektor dengan RRF, diukur precision dan recall.
- Image Generation API: gambar disimpan sekali di storage-mu dan diperiksa sebelum tayang.
- AI-Powered Forms & Content: AI menyarankan, manusia menyimpan, server memvalidasi.
- Fine-tuning vs Prompting: instruksi, contoh, dan RAG dulu; fine-tuning cuma kalau eval tetap gagal.
- Vector Databases: filter
toko_idgak memotong hasil di bawahLIMIT. - AI Agent Patterns:
MAKS_LANGKAH, dan tool tulis menunggu konfirmasi. - Evaluating AI Output: eval set jalan di CI, termasuk kasus antar-toko.
- Cost Optimization & Caching: routing, prompt caching, cache jawaban dengan
toko_iddan versi stok. - Self-hosted Models (Ollama): hitungan kapan self-host lebih murah, untuk toko yang datanya gak boleh keluar.
- AI Safety & Content Filtering: batas per pengguna, moderasi, hapus data pribadi,
safety_identifier. - Multi-modal AI in Web Apps: foto dikecilkan, EXIF dihapus, ukuran upload dibatasi.
⚠️ Jebakan yang sering ketemu
toko_iddiambil dari body request. Siapa pun bisa mengirimtoko_idtoko lain. Ambil dari API key yang sudah diverifikasi.- Cache jawaban tanpa
toko_id. Jawaban stok satu toko dibagikan ke pelanggan toko lain. - Harga rata untuk biaya yang beda 56 kali. Toko yang paling banyak memakai model mahal menghabiskan margin. Hitung kredit dari biaya sebenarnya.
- API key tenant disimpan apa adanya, atau kunci provider dibagikan ke toko. Simpan hash-nya saja; kunci provider gak pernah keluar dari server.
- Paket gratis per akun dan tanpa batas. Kuota per toko, berhenti di batas, dan verifikasi pendaftar.
TL;DR: AI SaaS menjual asisten ke banyak tenant. Pisahkan data dengan toko_id dari API key yang terverifikasi, di SQL, vector search, dan kunci cache; tanpa itu pelanggan Senja dapat stok Toko Pelangi. API key per toko dibuat acak (tb_live_ + 24 byte), ditampilkan sekali, disimpan sebagai hash SHA-256; kunci provider tetap satu di server. Stream lewat backend-mu dan catat usage dari response.completed per toko. Biaya per pertanyaan: stok $0,000041, rekomendasi $0,00231 (56 kali). Tantangan bisnis terbesarnya mengelola biaya AI per pengguna sambil menjaga margin: harga rata membuat Senja cuma 25,1%, sedangkan usage-based dengan tier dan kredit (stok 1, rekomendasi 50, Rp149.000 untuk 50.000 kredit, lebihnya Rp3) menjaga margin di atas 74%. Free tier dibatasi per toko. Pantau biaya dan eval per toko, pakai store: false, baca DPA provider, patuhi UU PDP, dan beri tahu pengguna bahwa mereka bicara dengan AI.