Panduan Ruby
Kiosapi kompatibel OpenAI lewat REST biasa. Contoh di bawah pakai Net::HTTP + json standar (tanpa gem tambahan) — untuk kasus chat sederhana, gem ruby-openai juga kompatibel (lihat contoh singkat di /docs).
Daftar isi
1. Persiapan
Masuk ke dashboard → Kunci API, beri nama, klik Buat key, salin kios_live_… (ditampilkan sekali).
export KIOSAPI_API_KEY="kios_live_xxxxxxxxxxxx"2. Fungsi helper request
require "net/http"
require "json"
require "uri"
BASE = "https://api.kiosapi.id/v1"
API_KEY = ENV.fetch("KIOSAPI_API_KEY")
def kiosapi_post(path, body)
uri = URI("#{BASE}#{path}")
req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{API_KEY}", "Content-Type" => "application/json")
req.body = body.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
JSON.parse(res.body)
end
def kiosapi_get(path)
uri = URI("#{BASE}#{path}")
req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{API_KEY}")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
JSON.parse(res.body)
endModel ID berformat provider/nama-model — lihat daftar lengkap via GET /v1/models atau /pricing.
3. Chat completion dasar
resp = kiosapi_post("/chat/completions", {
model: "anthropic/claude-sonnet-4-6",
messages: [
{ role: "system", content: "Kamu asisten yang ringkas dan ramah." },
{ role: "user", content: "Jelaskan apa itu RAG dalam 2 kalimat." },
],
})
puts resp.dig("choices", 0, "message", "content")
puts "Token dipakai: #{resp.dig('usage', 'total_tokens')}"4. Streaming
Tambahkan stream: true dan baca respons per-baris (SSE) pakai request_get/request_post dengan block streaming.
uri = URI("#{BASE}/chat/completions")
req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{API_KEY}", "Content-Type" => "application/json")
req.body = {
model: "deepseek/deepseek-v4-flash",
messages: [{ role: "user", content: "Tulis puisi pendek tentang hujan di Jakarta." }],
stream: true,
}.to_json
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.request(req) do |res|
res.read_body do |chunk|
chunk.each_line do |line|
next unless line.start_with?("data: ")
data = line.sub("data: ", "").strip
next if data == "[DONE]"
json = JSON.parse(data)
print json.dig("choices", 0, "delta", "content")
end
end
end
endModel reasoning (🧠) bisa mengirim delta reasoning_content terpisah dari content — cek key itu di JSON delta untuk status "sedang berpikir".
5. Tool calling / function calling
Format sama persis OpenAI — didukung untuk model bertanda 🔧 di katalog, termasuk Claude & Gemini (diterjemahkan otomatis oleh gateway).
tools = [{
type: "function",
function: {
name: "get_weather",
description: "Ambil cuaca terkini untuk sebuah kota",
parameters: {
type: "object",
properties: { city: { type: "string", description: "Nama kota" } },
required: ["city"],
},
},
}]
messages = [{ role: "user", content: "Cuaca di Bandung sekarang gimana?" }]
resp = kiosapi_post("/chat/completions", { model: "openai/gpt-4o", messages: messages, tools: tools })
msg = resp.dig("choices", 0, "message")
if msg["tool_calls"]
msg["tool_calls"].each do |call|
puts "Model minta panggil: #{call.dig('function', 'name')} #{call.dig('function', 'arguments')}"
messages << msg
messages << {
role: "tool", tool_call_id: call["id"],
content: { suhu_celsius: 27, kondisi: "berawan" }.to_json,
}
end
followup = kiosapi_post("/chat/completions", { model: "openai/gpt-4o", messages: messages })
puts followup.dig("choices", 0, "message", "content")
end6. Vision (kirim gambar ke model)
Kirim gambar lewat content berbentuk array (image_url + text) — format persis yang dipakai perintah lihat di Kiosapi CLI.
require "base64"
b64 = Base64.strict_encode64(File.read("foto.jpg"))
resp = kiosapi_post("/chat/completions", {
model: "google/gemini-2.5-flash", # pilih model yang mendukung vision
messages: [{
role: "user",
content: [
{ type: "text", text: "Ada apa saja di foto ini?" },
{ type: "image_url", image_url: { url: "data:image/jpeg;base64,#{b64}" } },
],
}],
})
puts resp.dig("choices", 0, "message", "content")Catatan: fitur ini sudah jalan di level API/CLI. Tombol upload gambar langsung di dashboard web belum tersedia.
7. Structured output / JSON mode
response_format diteruskan langsung ke provider upstream — jalan penuh untuk model yang mendukungnya secara native. Belum ada lapisan pemaksaan JSON yang seragam untuk semua model di katalog.
resp = kiosapi_post("/chat/completions", {
model: "openai/gpt-4o",
messages: [{ role: "user", content: "Beri saya data profil singkat dalam JSON: nama, umur." }],
response_format: { type: "json_object" },
})
data = JSON.parse(resp.dig("choices", 0, "message", "content"))
p data8. Embeddings
resp = kiosapi_post("/embeddings", {
model: "openai/text-embedding-3-small",
input: "Selamat datang di Kiosapi!",
})
vector = resp.dig("data", 0, "embedding")
puts "#{vector.length} dimensi"9. Daftar model & cek saldo
models = kiosapi_get("/models")
# Saldo & kuota gratis harian (endpoint khusus Kiosapi)
saldo = kiosapi_get("/saldo")
p saldo10. Endpoint khusus Kiosapi
Bentuknya beda dari chat completions standar, tapi tetap lewat helper kiosapi_post/kiosapi_get yang sama.
Gambar — POST /v1/images/generations (sinkron)
data = kiosapi_post("/images/generations", {
model: "google/imagen-3",
prompt: "kucing oranye memakai topi koki, fotorealistik",
option: "standard",
n: 1,
})
File.binwrite("hasil.png", Base64.decode64(data.dig("data", 0, "b64_json")))
puts "Biaya: #{data.dig('kiosapi', 'cost_rupiah')} rupiah"Video — POST /v1/videos/generations (asinkron, perlu polling)
submit = kiosapi_post("/videos/generations", {
model: "alibaba/wan2.7-t2v",
prompt: "ombak pantai saat matahari terbenam, sinematik",
option: "standard",
duration_seconds: 5,
})
job = nil
loop do
sleep 5
job = kiosapi_get("/jobs/#{submit['job_id']}")
break unless job["status"] == "running"
end
if job["status"] == "succeeded"
puts "Video siap: #{job['video_url']}"
else
puts "Gagal: #{job['error']}"
endImage-to-video: sertakan image (base64) + image_mime di body request.
Musik — POST /v1/music/generations
resp = kiosapi_post("/music/generations", {
model: "minimax/music-2.6",
prompt: "pop akustik Indonesia yang hangat, vokal wanita",
lyrics: "[Verse]\nPagi cerah di kota\n[Chorus]\nBersama kita bisa",
})
# poll GET /jobs/#{resp['job_id']} sampai status "succeeded" → field "audio_url"Text-to-speech — POST /v1/audio/speech
uri = URI("#{BASE}/audio/speech")
req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{API_KEY}", "Content-Type" => "application/json")
req.body = {
model: "minimax/speech-2.8-turbo",
input: "Selamat datang di Kiosapi!",
voice: "Indonesian_CalmWoman", # 9 suara asli Indonesia (model MiniMax)
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
File.binwrite("suara.mp3", res.body)Reranking — POST /v1/rerank (bentuk Cohere, self-hosted)
res = kiosapi_post("/rerank", {
model: "baai/bge-reranker-base",
query: "apa itu kucing?",
documents: [
"Kucing adalah hewan mamalia berkaki empat.",
"Mobil listrik semakin populer di Indonesia.",
],
return_documents: true,
})
res["results"].each { |r| puts "#{r['index']}: #{r['relevance_score']}" }Vector DB — POST /v1/vector-indexes/... (bentuk Pinecone, self-hosted)
# 1) Bikin index (gratis)
kiosapi_post("/vector-indexes", { name: "artikel-saya" })
# 2) Buat embedding lalu upsert (values wajib 1536 angka, cocok text-embedding-3-small)
emb = kiosapi_post("/embeddings", { model: "openai/text-embedding-3-small", input: "Isi artikel di sini" })
vector = emb.dig("data", 0, "embedding")
kiosapi_post("/vector-indexes/artikel-saya/vectors/upsert", {
vectors: [{ id: "artikel-1", values: vector, metadata: { kategori: "berita" } }],
})
# 3) Cari yang paling relevan
hasil = kiosapi_post("/vector-indexes/artikel-saya/query", {
vector: vector, top_k: 5, include_metadata: true,
})
p hasil["matches"]11. Penanganan error
def kiosapi_post_checked(path, body)
uri = URI("#{BASE}#{path}")
req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{API_KEY}", "Content-Type" => "application/json")
req.body = body.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
json = JSON.parse(res.body)
raise "Kiosapi error #{res.code}: #{json.dig('error', 'message') || res.body}" if res.code.to_i >= 400
json
endKode HTTP umum: 401 API key salah, 400 model tak dikenal/parameter salah, 402 saldo kurang atau batas pengeluaran bulanan tercapai, 422 ditolak moderasi, 429 rate limit, 502/504 provider upstream bermasalah/timeout. Kode error media lengkap ada di /docs.
12. Rate limit & kuota
| Free | Berbayar | |
|---|---|---|
| Rate limit | 5 req/menit | 60 req/menit |
| Kuota harian | 50 request/hari (reset 07:00 WIB), email terverifikasi | tanpa batas harian |
| Output maks | ~2048 token | sesuai maxOutput model |
| Input maks | ~24k karakter | sesuai kapasitas model |
13. Contoh lengkap — mini pipeline RAG
Menggabungkan embeddings + vector DB + chat dalam satu alur.
def embed(text)
kiosapi_post("/embeddings", { model: "openai/text-embedding-3-small", input: text }).dig("data", 0, "embedding")
end
# Index dokumen (sekali saja)
kiosapi_post("/vector-indexes", { name: "basis-pengetahuan" })
dokumen = ["Kiosapi adalah AI API gateway Indonesia.", "Kiosapi mendukung 100+ model AI."]
vectors = dokumen.each_with_index.map { |d, i| { id: "doc-#{i}", values: embed(d), metadata: { text: d } } }
kiosapi_post("/vector-indexes/basis-pengetahuan/vectors/upsert", { vectors: vectors })
# Query + jawab pakai konteks yang relevan
pertanyaan = "Apa itu Kiosapi?"
hasil = kiosapi_post("/vector-indexes/basis-pengetahuan/query", {
vector: embed(pertanyaan), top_k: 2, include_metadata: true,
})
konteks = hasil["matches"].map { |m| m.dig("metadata", "text") }.join("\n")
jawaban = kiosapi_post("/chat/completions", {
model: "anthropic/claude-sonnet-4-6",
messages: [
{ role: "system", content: "Jawab berdasar konteks ini:\n#{konteks}" },
{ role: "user", content: pertanyaan },
],
})
puts jawaban.dig("choices", 0, "message", "content")Referensi lain: spesifikasi OpenAPI 3.1 lengkap, harga & daftar model di /pricing, dan dokumentasi utama di /docs.