KKiosapi.id

← Dokumentasi

Panduan Go

Kiosapi kompatibel OpenAI lewat REST biasa. Contoh di bawah pakai net/http + encoding/json standar (tanpa dependensi eksternal) supaya jalan di proyek Go mana pun — untuk kasus chat sederhana, package github.com/sashabaranov/go-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).

Keamanan: panggil API dari server, bukan dari kode WASM/frontend. Simpan key sebagai environment variable, jangan hardcode.
export KIOSAPI_API_KEY="kios_live_xxxxxxxxxxxx"

2. Fungsi helper request

package kiosapi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

const BaseURL = "https://api.kiosapi.id/v1"

func apiKey() string { return os.Getenv("KIOSAPI_API_KEY") }

func Post(path string, body any, out any) error {
	buf, _ := json.Marshal(body)
	req, _ := http.NewRequest("POST", BaseURL+path, bytes.NewReader(buf))
	req.Header.Set("Authorization", "Bearer "+apiKey())
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		return fmt.Errorf("kiosapi error: HTTP %d", resp.StatusCode)
	}
	return json.NewDecoder(resp.Body).Decode(out)
}

func Get(path string, out any) error {
	req, _ := http.NewRequest("GET", BaseURL+path, nil)
	req.Header.Set("Authorization", "Bearer "+apiKey())
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	return json.NewDecoder(resp.Body).Decode(out)
}

Model ID berformat provider/nama-model — lihat daftar lengkap via GET /v1/models atau /pricing.

3. Chat completion dasar

type ChatResponse struct {
	Choices []struct {
		Message struct {
			Content string `json:"content"`
		} `json:"message"`
	} `json:"choices"`
	Usage struct {
		TotalTokens int `json:"total_tokens"`
	} `json:"usage"`
}

var resp ChatResponse
err := kiosapi.Post("/chat/completions", map[string]any{
	"model": "anthropic/claude-sonnet-4-6",
	"messages": []map[string]string{
		{"role": "system", "content": "Kamu asisten yang ringkas dan ramah."},
		{"role": "user", "content": "Jelaskan apa itu RAG dalam 2 kalimat."},
	},
}, &resp)
if err != nil {
	panic(err)
}
fmt.Println(resp.Choices[0].Message.Content)
fmt.Println("Token dipakai:", resp.Usage.TotalTokens)

4. Streaming

Tambahkan "stream": true dan baca respons baris demi baris (SSE).

import "bufio"
import "strings"

body, _ := json.Marshal(map[string]any{
	"model": "deepseek/deepseek-v4-flash",
	"messages": []map[string]string{
		{"role": "user", "content": "Tulis puisi pendek tentang hujan di Jakarta."},
	},
	"stream": true,
})
req, _ := http.NewRequest("POST", kiosapi.BaseURL+"/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey())
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
	line := scanner.Text()
	if !strings.HasPrefix(line, "data: ") || line == "data: [DONE]" {
		continue
	}
	var chunk struct {
		Choices []struct {
			Delta struct {
				Content string `json:"content"`
			} `json:"delta"`
		} `json:"choices"`
	}
	json.Unmarshal([]byte(strings.TrimPrefix(line, "data: ")), &chunk)
	if len(chunk.Choices) > 0 {
		fmt.Print(chunk.Choices[0].Delta.Content)
	}
}

Model reasoning (🧠) bisa mengirim delta reasoning_content terpisah dari content — tambahkan field itu ke struct Delta untuk menampilkan 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 := []map[string]any{{
	"type": "function",
	"function": map[string]any{
		"name":        "get_weather",
		"description": "Ambil cuaca terkini untuk sebuah kota",
		"parameters": map[string]any{
			"type":       "object",
			"properties": map[string]any{"city": map[string]string{"type": "string", "description": "Nama kota"}},
			"required":   []string{"city"},
		},
	},
}}

messages := []map[string]any{{"role": "user", "content": "Cuaca di Bandung sekarang gimana?"}}

var resp struct {
	Choices []struct {
		Message struct {
			Content   string `json:"content"`
			ToolCalls []struct {
				ID       string `json:"id"`
				Function struct {
					Name      string `json:"name"`
					Arguments string `json:"arguments"`
				} `json:"function"`
			} `json:"tool_calls"`
		} `json:"message"`
	} `json:"choices"`
}
kiosapi.Post("/chat/completions", map[string]any{
	"model": "openai/gpt-4o", "messages": messages, "tools": tools,
}, &resp)

for _, call := range resp.Choices[0].Message.ToolCalls {
	fmt.Println("Model minta panggil:", call.Function.Name, call.Function.Arguments)
	messages = append(messages, map[string]any{"role": "assistant", "tool_calls": resp.Choices[0].Message.ToolCalls})
	messages = append(messages, map[string]any{
		"role": "tool", "tool_call_id": call.ID,
		"content": `{"suhu_celsius": 27, "kondisi": "berawan"}`,
	})
}
var followup ChatResponse
kiosapi.Post("/chat/completions", map[string]any{"model": "openai/gpt-4o", "messages": messages}, &followup)
fmt.Println(followup.Choices[0].Message.Content)

6. Vision (kirim gambar ke model)

Kirim gambar lewat content berbentuk array (image_url + text) — format persis yang dipakai perintah lihat di Kiosapi CLI.

import (
	"encoding/base64"
	"os"
)

raw, _ := os.ReadFile("foto.jpg")
b64 := base64.StdEncoding.EncodeToString(raw)

var resp ChatResponse
kiosapi.Post("/chat/completions", map[string]any{
	"model": "google/gemini-2.5-flash", // pilih model yang mendukung vision
	"messages": []map[string]any{{
		"role": "user",
		"content": []map[string]any{
			{"type": "text", "text": "Ada apa saja di foto ini?"},
			{"type": "image_url", "image_url": map[string]string{"url": "data:image/jpeg;base64," + b64}},
		},
	}},
}, &resp)
fmt.Println(resp.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.

var resp ChatResponse
kiosapi.Post("/chat/completions", map[string]any{
	"model":           "openai/gpt-4o",
	"messages":        []map[string]string{{"role": "user", "content": "Beri saya data profil singkat dalam JSON: nama, umur."}},
	"response_format": map[string]string{"type": "json_object"},
}, &resp)

var data map[string]any
json.Unmarshal([]byte(resp.Choices[0].Message.Content), &data)
fmt.Println(data)

8. Embeddings

var resp struct {
	Data []struct {
		Embedding []float64 `json:"embedding"`
	} `json:"data"`
}
kiosapi.Post("/embeddings", map[string]any{
	"model": "openai/text-embedding-3-small",
	"input": "Selamat datang di Kiosapi!",
}, &resp)
vector := resp.Data[0].Embedding
fmt.Println(len(vector), "dimensi")

9. Daftar model & cek saldo

var models any
kiosapi.Get("/models", &models)

// Saldo & kuota gratis harian (endpoint khusus Kiosapi)
var saldo any
kiosapi.Get("/saldo", &saldo)
fmt.Println(saldo)

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

var img struct {
	Data []struct {
		B64Json string `json:"b64_json"`
	} `json:"data"`
	Kiosapi struct {
		CostRupiah int `json:"cost_rupiah"`
	} `json:"kiosapi"`
}
kiosapi.Post("/images/generations", map[string]any{
	"model":  "google/imagen-3",
	"prompt": "kucing oranye memakai topi koki, fotorealistik",
	"option": "standard",
	"n":      1,
}, &img)

raw, _ := base64.StdEncoding.DecodeString(img.Data[0].B64Json)
os.WriteFile("hasil.png", raw, 0644)
fmt.Println("Biaya:", img.Kiosapi.CostRupiah, "rupiah")

Video — POST /v1/videos/generations (asinkron, perlu polling)

import "time"

var submit struct {
	JobID string `json:"job_id"`
}
kiosapi.Post("/videos/generations", map[string]any{
	"model":            "alibaba/wan2.7-t2v",
	"prompt":           "ombak pantai saat matahari terbenam, sinematik",
	"option":           "standard",
	"duration_seconds": 5,
}, &submit)

var job struct {
	Status   string `json:"status"`
	VideoURL string `json:"video_url"`
	Error    string `json:"error"`
}
for {
	time.Sleep(5 * time.Second)
	kiosapi.Get("/jobs/"+submit.JobID, &job)
	if job.Status != "running" {
		break
	}
}
if job.Status == "succeeded" {
	fmt.Println("Video siap:", job.VideoURL)
} else {
	fmt.Println("Gagal:", job.Error)
}

Image-to-video: sertakan image (base64) + image_mime di body request.

Musik — POST /v1/music/generations

var resp struct {
	JobID string `json:"job_id"`
}
kiosapi.Post("/music/generations", map[string]any{
	"model":  "minimax/music-2.6",
	"prompt": "pop akustik Indonesia yang hangat, vokal wanita",
	"lyrics": "[Verse]\nPagi cerah di kota\n[Chorus]\nBersama kita bisa",
}, &resp)
// poll GET /jobs/{resp.JobID} sampai status "succeeded" → field "audio_url"

Text-to-speech — POST /v1/audio/speech

body, _ := json.Marshal(map[string]any{
	"model": "minimax/speech-2.8-turbo",
	"input": "Selamat datang di Kiosapi!",
	"voice": "Indonesian_CalmWoman", // 9 suara asli Indonesia (model MiniMax)
})
req, _ := http.NewRequest("POST", kiosapi.BaseURL+"/audio/speech", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey())
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
audio, _ := io.ReadAll(resp.Body)
os.WriteFile("suara.mp3", audio, 0644)

Reranking — POST /v1/rerank (bentuk Cohere, self-hosted)

var resp struct {
	Results []struct {
		Index          int     `json:"index"`
		RelevanceScore float64 `json:"relevance_score"`
	} `json:"results"`
}
kiosapi.Post("/rerank", map[string]any{
	"model": "baai/bge-reranker-base",
	"query": "apa itu kucing?",
	"documents": []string{
		"Kucing adalah hewan mamalia berkaki empat.",
		"Mobil listrik semakin populer di Indonesia.",
	},
	"return_documents": true,
}, &resp)
for _, r := range resp.Results {
	fmt.Println(r.Index, r.RelevanceScore)
}

Vector DB — POST /v1/vector-indexes/... (bentuk Pinecone, self-hosted)

// 1) Bikin index (gratis)
var created any
kiosapi.Post("/vector-indexes", map[string]any{"name": "artikel-saya"}, &created)

// 2) Buat embedding lalu upsert (values wajib 1536 angka, cocok text-embedding-3-small)
var emb struct {
	Data []struct{ Embedding []float64 `json:"embedding"` } `json:"data"`
}
kiosapi.Post("/embeddings", map[string]any{"model": "openai/text-embedding-3-small", "input": "Isi artikel di sini"}, &emb)
vector := emb.Data[0].Embedding

var upserted any
kiosapi.Post("/vector-indexes/artikel-saya/vectors/upsert", map[string]any{
	"vectors": []map[string]any{{"id": "artikel-1", "values": vector, "metadata": map[string]string{"kategori": "berita"}}},
}, &upserted)

// 3) Cari yang paling relevan
var hasil struct {
	Matches []map[string]any `json:"matches"`
}
kiosapi.Post("/vector-indexes/artikel-saya/query", map[string]any{
	"vector": vector, "top_k": 5, "include_metadata": true,
}, &hasil)
fmt.Println(hasil.Matches)

11. Penanganan error

var resp ChatResponse
err := kiosapi.Post("/chat/completions", map[string]any{
	"model":    "openai/gpt-4o",
	"messages": []map[string]string{{"role": "user", "content": "Halo!"}},
}, &resp)
if err != nil {
	// kiosapi.Post sudah membungkus status HTTP >= 400 sebagai error
	log.Fatal(err)
}

Kode 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

FreeBerbayar
Rate limit5 req/menit60 req/menit
Kuota harian50 request/hari (reset 07:00 WIB), email terverifikasitanpa batas harian
Output maks~2048 tokensesuai maxOutput model
Input maks~24k karaktersesuai kapasitas model

13. Contoh lengkap — mini pipeline RAG

Menggabungkan embeddings + vector DB + chat dalam satu alur.

func embed(text string) []float64 {
	var resp struct {
		Data []struct{ Embedding []float64 `json:"embedding"` } `json:"data"`
	}
	kiosapi.Post("/embeddings", map[string]any{"model": "openai/text-embedding-3-small", "input": text}, &resp)
	return resp.Data[0].Embedding
}

func main() {
	// Index dokumen (sekali saja)
	var created any
	kiosapi.Post("/vector-indexes", map[string]any{"name": "basis-pengetahuan"}, &created)

	dokumen := []string{"Kiosapi adalah AI API gateway Indonesia.", "Kiosapi mendukung 100+ model AI."}
	var vectors []map[string]any
	for i, d := range dokumen {
		vectors = append(vectors, map[string]any{
			"id": fmt.Sprintf("doc-%d", i), "values": embed(d), "metadata": map[string]string{"text": d},
		})
	}
	var upserted any
	kiosapi.Post("/vector-indexes/basis-pengetahuan/vectors/upsert", map[string]any{"vectors": vectors}, &upserted)

	// Query + jawab pakai konteks yang relevan
	pertanyaan := "Apa itu Kiosapi?"
	var hasil struct {
		Matches []struct {
			Metadata struct{ Text string `json:"text"` } `json:"metadata"`
		} `json:"matches"`
	}
	kiosapi.Post("/vector-indexes/basis-pengetahuan/query", map[string]any{
		"vector": embed(pertanyaan), "top_k": 2, "include_metadata": true,
	}, &hasil)

	var konteks strings.Builder
	for _, m := range hasil.Matches {
		konteks.WriteString(m.Metadata.Text + "\n")
	}

	var jawaban ChatResponse
	kiosapi.Post("/chat/completions", map[string]any{
		"model": "anthropic/claude-sonnet-4-6",
		"messages": []map[string]string{
			{"role": "system", "content": "Jawab berdasar konteks ini:\n" + konteks.String()},
			{"role": "user", "content": pertanyaan},
		},
	}, &jawaban)
	fmt.Println(jawaban.Choices[0].Message.Content)
}

Referensi lain: spesifikasi OpenAPI 3.1 lengkap, harga & daftar model di /pricing, dan dokumentasi utama di /docs.