Cara Pakai OpenAI Decisions API: Bangun Router Chat CS dengan Contoh Kode
Oleh Tim APIPedia, Tim Engineering APIPedia · Terbit · 10 menit baca
Tutorial OpenAI Decisions API (GA 22 Okt 2026): klasifikasi pesan CS dengan predicate, choice, score, lalu teruskan ke API Cek Ongkir APIPedia.

OpenAI Decisions API adalah endpoint khusus (POST /v1/decisions) yang menjawab pertanyaan klasifikasi dalam bentuk terstruktur: probabilitas, pilihan, atau skor, bukan teks bebas. Sejak resmi GA pada 22 Oktober 2026, banyak tim memakainya untuk routing pesan karena murah ($0,10 per 1 juta token input, output gratis) dan jauh lebih cepat dari model chat.
Di tutorial ini kita membangun router chat customer service untuk toko online: satu panggilan Decisions API menentukan maksud pesan, apakah perlu agen manusia, dan seberapa kesal pelanggan. Untuk pertanyaan ongkir, router langsung memanggil API Cek Ongkir APIPedia. Semua kode bisa dijalankan dengan Node.js 18+.
Apa bedanya Decisions API dengan Responses API biasa?
Responses API (atau chat completions) menghasilkan teks. Kalau Anda memakainya untuk klasifikasi, Anda harus menulis prompt "jawab hanya dengan salah satu label ini", lalu mem-parsing teksnya dan berharap model patuh. Decisions API memotong semua itu: Anda mengirim bukti (input) dan daftar pertanyaan (questions), lalu menerima array answers yang sudah bertipe.
Menurut dokumentasi resmi OpenAI, endpoint ini hanya mendukung model gpt-6-luna dan mengembalikan jawaban sekitar 10 kali lebih cepat dibanding memanggil model yang sama lewat Responses API. Angka absolut latensi tidak dicantumkan di dokumentasi; ringkasan OODA Loop menyebut kisaran 100–180 ms per respons, tetapi itu belum diverifikasi independen.
| Aspek | Decisions API | Responses API untuk klasifikasi |
|---|---|---|
| Bentuk output | JSON bertipe: probability, choice, score | Teks bebas, perlu parsing |
| Model | Hanya gpt-6-luna | Banyak pilihan |
| Biaya output | Tidak ditagih | Ditagih per token |
| Harga input (tanpa cache) | $0,10 / 1 juta token | Tergantung model |
| Pertanyaan per request | 1–400 (beta: 200) | Satu prompt, satu jawaban |
| Bisa menulis balasan? | Tidak | Ya |
Poin terakhir penting: Decisions API tidak menulis kalimat balasan dan tidak mengekstrak entitas seperti nama kota. Ia hanya memutuskan. Jadi pola yang masuk akal adalah: Decisions API untuk routing, lalu kode biasa, API data, atau model chat untuk langkah berikutnya.
Tiga tipe pertanyaan: predicate, choice, score
- predicate: peluang sebuah kondisi benar, dari 0 sampai 1. Contoh: "Apakah pelanggan minta bicara dengan manusia?"
- choice: memilih satu nilai dari daftar Anda. Jawabannya berisi
choice,probabilitiestiap opsi, danconfidence. Sertakan opsi cadangan sepertilainnyaagar input aneh tidak dipaksa masuk kategori yang salah. - score: menilai input pada 2–10 level berurutan. Nilai
scoreadalah rata-rata tertimbang indeks level (mulai 0), jadi bisa berupa pecahan seperti 1,6.
Ada satu tipe jawaban tambahan: refusal, ketika model menolak menjawab satu pertanyaan. Kode Anda wajib menanganinya terpisah.

Langkah 1: Uji coba pertama dengan curl
Siapkan API key OpenAI di variabel lingkungan, lalu kirim satu pertanyaan predicate. Struktur body mengikuti dokumentasi resmi: model, input, dan questions.
export OPENAI_API_KEY="sk-..."
curl -s https://api.openai.com/v1/decisions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-luna",
"input": "Kak, paket saya dari minggu lalu belum sampai. Bisa sambungkan ke admin?",
"questions": [
{
"type": "predicate",
"name": "butuh_manusia",
"instructions": "Apakah pelanggan secara eksplisit meminta dilayani oleh manusia/admin?"
}
]
}'
Responsnya berisi array answers (satu per pertanyaan) dan objek usage. Bentuknya kira-kira seperti ini; angka di bawah hanya ilustrasi:
{
"answers": [
{ "type": "predicate", "name": "butuh_manusia", "probability": 0.93 }
],
"usage": {
"input_tokens": 61,
"input_tokens_details": { "cached_tokens": 0 }
}
}
Langkah 2: Rancang pertanyaan untuk router CS
Satu request bisa memuat sampai 400 pertanyaan, dan semuanya berbagi input yang sama. Untuk router kita cukup tiga:
- intent (choice):
cek_ongkir,jadwal_kirim,lacak_paket,komplain,lainnya. - butuh_manusia (predicate): apakah perlu dialihkan ke agen.
- emosi (score, 4 level): tenang, sedikit kesal, kesal, sangat marah.
Data pesanan pelanggan bisa ikut dikirim sebagai part input_json di pesan user. Fitur ini baru hadir saat GA dan hanya berlaku di pesan berperan user; isi field data diperlakukan sebagai data biasa, bukan instruksi.

Langkah 3: Fungsi klasifikasi di Node.js
Kode di bawah memakai fetch bawaan Node.js 18+, tanpa dependensi. Simpan sebagai decide.mjs.
// decide.mjs
const OPENAI_URL = "https://api.openai.com/v1/decisions";
const QUESTIONS = [
{
type: "choice",
name: "intent",
instructions: "Apa maksud utama pesan pelanggan toko online ini?",
choices: [
{ value: "cek_ongkir", description: "Menanyakan ongkos kirim ke suatu kota" },
{ value: "jadwal_kirim", description: "Menanyakan kapan paket dikirim atau apakah toko libur" },
{ value: "lacak_paket", description: "Menanyakan posisi paket yang sudah dikirim" },
{ value: "komplain", description: "Mengeluhkan produk rusak, salah kirim, atau pelayanan" },
{ value: "lainnya", description: "Tidak cocok dengan kategori mana pun" }
]
},
{
type: "predicate",
name: "butuh_manusia",
instructions: "Apakah percakapan ini sebaiknya dialihkan ke agen manusia (diminta eksplisit, ada ancaman, atau masalah pembayaran)?"
},
{
type: "score",
name: "emosi",
instructions: "Seberapa kesal pelanggan berdasarkan nada pesannya?",
levels: [
{ label: "tenang", description: "Bertanya biasa, tanpa keluhan" },
{ label: "sedikit_kesal", description: "Ada nada tidak sabar" },
{ label: "kesal", description: "Mengeluh jelas atau mengulang pertanyaan" },
{ label: "sangat_marah", description: "Marah, mengancam, atau memakai huruf kapital berlebihan" }
]
}
];
export async function decide(pesan, dataPesanan = null) {
const content = [{ type: "input_text", text: pesan }];
if (dataPesanan) content.push({ type: "input_json", data: dataPesanan });
const res = await fetch(OPENAI_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "gpt-6-luna",
input: [{ role: "user", content }],
questions: QUESTIONS
})
});
if (!res.ok) throw new Error(`Decisions API ${res.status}: ${await res.text()}`);
const { answers } = await res.json();
// Ubah array answers menjadi objek { intent: {...}, butuh_manusia: {...}, ... }
return Object.fromEntries(answers.map((a) => [a.name, a]));
}
Kalau Anda lebih suka SDK resmi, dokumentasi OpenAI menyebut pola yang sama lewat client.decisions.create({ model, input, questions }) di SDK JavaScript maupun Python, dengan nama field yang identik.
Langkah 4: Router dengan ambang batas dan API Cek Ongkir APIPedia
Inilah bagian yang sering dilupakan: probabilitas tidak berguna tanpa ambang batas. Aturan kita:
- Jika ada jawaban
refusalatauconfidenceintent di bawah 0,6, minta pelanggan memperjelas. - Jika
butuh_manusia≥ 0,7 atauemosi≥ 2,0 (level "kesal" ke atas), langsung eskalasi. - Selain itu, jalankan aksi sesuai intent.
Untuk intent cek_ongkir, kota tujuan diambil dari kolom formulir chat (Decisions API tidak mengekstrak teks). Lalu kita panggil dua endpoint APIPedia: /v1/ongkir/search untuk mencari id wilayah dan /v1/ongkir/cek untuk estimasi tarif. Untuk jadwal_kirim, kita cek apakah besok hari libur nasional lewat /v1/libur/cek/{tanggal}.
// router.mjs
import { decide } from "./decide.mjs";
const APIPEDIA = "https://api.apipedia.id/v1";
const headers = { Authorization: `Bearer ${process.env.APIPEDIA_KEY}` };
const ORIGIN_ID = "3273"; // KOTA BANDUNG, id dari /v1/ongkir/search?q=bandung
async function apipedia(path) {
const res = await fetch(`${APIPEDIA}${path}`, { headers });
const body = await res.json();
if (!body.is_success) throw new Error(body.message);
return body.data;
}
async function cekOngkir(kotaTujuan, beratGram) {
const cari = await apipedia(`/ongkir/search?q=${encodeURIComponent(kotaTujuan)}`);
const tujuan = cari.hasil.find((w) => w.tipe === "kabupaten") ?? cari.hasil[0];
if (!tujuan) return `Kota "${kotaTujuan}" belum ketemu, boleh tulis nama kabupaten/kotanya?`;
const data = await apipedia(
`/ongkir/cek?origin=${ORIGIN_ID}&destination=${tujuan.id}&weight=${beratGram}`
);
const termurah = [...data.tarif].sort((a, b) => a.total - b.total).slice(0, 3);
return `Estimasi ongkir ke ${tujuan.nama} (${data.berat_tagih_kg} kg): ` +
termurah.map((t) => `${t.nama} Rp${t.total.toLocaleString("id-ID")}`).join(", ");
}
async function cekJadwal() {
const besok = new Date(Date.now() + 86400000)
.toLocaleDateString("en-CA", { timeZone: "Asia/Jakarta" }); // YYYY-MM-DD
const d = await apipedia(`/libur/cek/${besok}`);
return d.is_libur
? `Besok ${d.keterangan}, pengiriman dilanjutkan di hari kerja berikutnya.`
: "Pesanan sebelum jam 15.00 dikirim besok.";
}
export async function route({ pesan, kotaTujuan, beratGram = 1000, pesanan }) {
const r = await decide(pesan, pesanan);
if (Object.values(r).some((a) => a.type === "refusal")) return { aksi: "eskalasi" };
if (r.butuh_manusia.probability >= 0.7 || r.emosi.score >= 2.0) {
return { aksi: "eskalasi", prioritas: r.emosi.score >= 2.0 ? "tinggi" : "normal" };
}
if (r.intent.confidence < 0.6) return { aksi: "klarifikasi" };
switch (r.intent.choice) {
case "cek_ongkir":
return { aksi: "balas", teks: await cekOngkir(kotaTujuan, beratGram) };
case "jadwal_kirim":
return { aksi: "balas", teks: await cekJadwal() };
case "lacak_paket":
case "komplain":
return { aksi: "tiket", kategori: r.intent.choice };
default:
return { aksi: "klarifikasi" };
}
}
// Uji cepat: node router.mjs
if (import.meta.url === `file://${process.argv[1]}`) {
console.log(await route({
pesan: "Halo kak, kirim 1,5 kg ke Surabaya kira-kira berapa ya?",
kotaTujuan: "surabaya",
beratGram: 1500
}));
}
Jalankan dengan:
export OPENAI_API_KEY="sk-..."
export APIPEDIA_KEY="apk_..." # buat di https://console.apipedia.id
node router.mjs
Saat kami menguji endpoint APIPedia untuk rute Bandung (3273) ke Kota Surabaya (3578) seberat 1.500 gram, berat tagihnya dibulatkan menjadi 2 kg dan tarif termurah yang keluar adalah POS Kilat Khusus Rp29.854. Ingat, ini estimasi dari data statis, bukan tarif resmi kurir. Panduan lengkap endpoint ongkir ada di artikel API Cek Ongkir Indonesia, dan detail kalender libur di API Hari Libur Nasional Indonesia.
Langkah 5: Hemat biaya dengan prompt caching
Kalau setiap request membawa konteks panjang yang sama (misalnya kebijakan toko atau katalog singkat), letakkan konteks itu di awal input. Dokumentasi OpenAI menjelaskan ada breakpoint cache implisit di akhir input, dengan syarat prefix yang bisa dipakai ulang minimal 1.024 token; isi questions tidak dihitung. Anda bisa memisahkan cache per tenant dengan prompt_cache_key.
Soal harga cache, sumber sedikit berbeda. Dokumentasi resmi hanya menyebut cache hit "didiskon" dan penulisan cache ditagih sesuai tarif caching gpt-6-luna. Kju dan OODA Loop menulis angka $0,01 per 1 juta token untuk input yang ter-cache. Cek halaman harga resmi sebelum menghitung anggaran.
Langkah 6: Pantau pemakaian
Sejak GA ada endpoint usage khusus. Endpoint ini butuh admin API key dengan izin api.usage.read, bukan key proyek biasa. Perhatikan bahwa num_model_requests menghitung request, bukan jumlah pertanyaan.
curl -s "https://api.openai.com/v1/organization/usage/decisions?start_time=$(date -d '7 days ago' +%s)" \
-H "Authorization: Bearer $OPENAI_ADMIN_KEY"
Hitungan kasar: satu pesan CS sekitar 150 token input. Dengan harga $0,10 per 1 juta token, 100.000 pesan (15 juta token) kira-kira $1,50 sebelum cache. Angka ini belum termasuk premi pemrosesan regional atau pengali konteks panjang yang disebut dokumentasi.
Batasan yang perlu Anda tahu
- Satu model saja: hanya
gpt-6-luna. - Input yang tidak didukung: file, audio, dan
file_id. Gambar boleh lewat URL publik HTTP/HTTPS atau data URL base64. - Region: pemrosesan tersedia di Amerika Serikat dan Eropa (EEA dan Swiss). Belum ada opsi region Asia, jadi pertimbangkan aturan data pribadi (UU PDP) sebelum mengirim data pelanggan. Kirim seperlunya saja, misalnya tanpa NIK atau nomor HP.
- Tanpa teks balasan: kalau butuh jawaban natural, pakai model chat setelah routing.
- Klaim kecepatan: "10x lebih cepat" adalah klaim OpenAI untuk tugas keputusan saja. OneMetrik mengingatkan bahwa waktu total alur kerja tetap bisa didominasi panggilan database, jaringan, atau persetujuan manual.
FAQ
Apakah Decisions API bisa dipakai dari Indonesia?
Bisa, selama akun OpenAI Anda aktif dan punya akses API. Yang perlu diperhatikan adalah lokasi pemrosesan data (AS atau Eropa) dan latensi jaringan dari server Anda.
Berapa banyak pertanyaan yang sebaiknya dimasukkan ke satu request?
Batasnya 1–400. Gabungkan pertanyaan yang memakai bukti (input) yang sama dalam satu request agar input hanya dibayar sekali. Pertanyaan untuk pesan yang berbeda tetap harus request terpisah.
Kenapa skor emosi saya 1,6, bukan bilangan bulat?
Karena score adalah rata-rata tertimbang indeks level. Nilai 1,6 berarti model condong ke "kesal" (indeks 2) tetapi masih ada peluang "sedikit kesal". Lihat array probabilities kalau Anda butuh distribusi lengkap.
Apa yang terjadi kalau pesan tidak cocok dengan pilihan mana pun?
Model tetap harus memilih salah satu nilai di choices. Itu sebabnya opsi lainnya dan pengecekan confidence penting supaya pesan aneh tidak salah rute.
Apakah API APIPedia bisa dipanggil dari agen AI lain?
Bisa. Selain REST seperti contoh di atas, APIPedia juga tersedia sebagai server MCP. Lihat panduan menghubungkan APIPedia ke Claude lewat MCP.
Sumber
Topik: #openai #decisions api #tutorial #ai #nodejs #cek ongkir
Tentang penulis
Tim APIPedia — Tim Engineering APIPedia
Tim engineering di balik APIPedia yang membangun dan memelihara API data Indonesia - ongkir, OCR KTP, data wilayah, BMKG, hingga server MCP - dan menulis panduan integrasinya dari pengalaman langsung.