Semua tulisan

Request pertama dari nol

Tim kenari5 menit baca
Request pertama dari nol

Dari dashboard kosong sampai jawaban pertama butuh tiga langkah dan sekitar lima menit. Modalnya akun di kenari.id dan terminal yang bisa menjalankan curl.

Mengirim request-nya gampang. Yang bikin orang habis waktu berjam-jam justru base URL: bentuknya berbeda tergantung klien, dan kalau salah, yang balik cuma 405 kosong yang sama sekali nggak kelihatan seperti masalah path.

Tiga langkah

1. Buat key

Halaman API Keys

Buat API Key baru

Masuk ke dashboard, buka API keys, klik Buat key. Key-nya diawali kn- dan cuma muncul sekali, karena yang kami simpan setelahnya tinggal hash SHA-256. Kami sendiri nggak bisa membaca nilai aslinya. Salin sekarang, simpan di tempat yang nggak ikut ter-commit, dan kalau hilang, bikin key baru lalu cabut yang lama.

Satu key ini berlaku untuk semua model, semua provider di belakang gateway, dan dua bentuk API sekaligus, OpenAI maupun Anthropic. Ganti model nggak pernah butuh key kedua.

2. Lewati saldo

Menu Saldo belum kepakai. Tambahkan akhiran :free di id model, dan request-nya nggak memotong apa pun.

Waktu kami cek sambil menulis ini, step-3-7-flash:free ada di katalog publik dengan pricing.free: true. Konsekuensinya, ada limit per menit dan jatah harian per akun. Angka terbarunya ada di halaman paket.

Kalau kamu terlanjur mengirim model berbayar sementara saldonya Rp 0, yang balik 402 insufficient_balance. Yang kosong saldonya, bukan key-nya.

3. Kirim request

Ganti kn-... dengan key yang barusan kamu salin.

curl https://kenari.id/v1/chat/completions \
  -H "Authorization: Bearer kn-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "step-3-7-flash:free",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'

Ini cuplikan resmi dari mulai cepat. Yang wajib cuma dua field, model dan messages, dan jawabannya ada di choices[0].message.content. Autentikasinya lewat header Authorization: Bearer, walaupun sebagian klien mengirim x-api-key dan itu juga diterima.

Responsnya memakai format chat.completion, persis seperti OpenAI, jadi kalau kamu pernah memanggil OpenAI langsung, isinya nggak asing:

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "step-3-7-flash:free",
  "choices": [
    { "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" }
  ],
  "usage": { "prompt_tokens": 12, "completion_tokens": 9, "total_tokens": 21 }
}

usage tetap terisi walaupun modelnya :free. Token tetap dihitung supaya kamu tahu seberapa besar request-mu. Yang Rp 0 cuma biayanya.

Klien kamu yang menentukan bentuknya

Kami coba jalankan ketiganya. Key sama, body sama, cuma path-nya yang beda:

K='Authorization: Bearer kn-...'
B='{"model":"step-3-7-flash:free","messages":[{"role":"user","content":"Hello!"}]}'

for path in /v1/v1/chat/completions /chat/completions /v1/chat/completions; do
  curl -s -w " <- %{http_code} $path\n" -X POST "https://kenari.id$path" \
    -H "$K" -H 'Content-Type: application/json' -d "$B"
done
 <- 405 /v1/v1/chat/completions
 <- 405 /chat/completions
invalid key <- 401 /v1/chat/completions

Dua 405 itu balik dengan body kosong, tanpa content type. Nggak ada yang bisa dibaca, nggak ada keterangan bahwa path-nya salah. Sementara 401 di path yang benar berupa teks biasa dan menyebut invalid key.

Logikanya kebalik dari dugaan kebanyakan orang. Body kosong artinya path-mu nggak cocok dengan route mana pun, jadi gateway-nya belum sempat menilai key-mu sama sekali. Begitu body-nya kebaca, walaupun isinya marah-marah, berarti path-nya cocok dan gateway-nya sudah menjawab kamu. Makanya banyak yang melihat 405 kosong itu, menyimpulkan key-nya rusak, lalu bikin key baru. Padahal key-nya nggak pernah bermasalah.

Klien OpenAI (SDK resmi, curl ke /chat/completions, hampir semua wrapper yang meniru OpenAI) minta base URL tepat https://kenari.id/v1, karena SDK-nya sendiri yang menempelkan sisa path di belakangnya. Dua 405 di atas datang dari sini: yang pertama menulis /v1 dua kali, yang kedua malah menghilangkannya.

Claude Code justru sebaliknya, karena dia menambahkan /v1 sendiri, jadi base URL yang kamu isi cukup https://kenari.id:

export ANTHROPIC_BASE_URL=https://kenari.id
export ANTHROPIC_AUTH_TOKEN=kn-...
export ANTHROPIC_MODEL=step-3-7-flash:free

SDK Anthropic beda dari keduanya, karena dokumen Messages-nya memakai base_url="https://kenari.id/v1", dan payload yang dikirim pun beda, ke path yang beda pula:

curl https://kenari.id/v1/messages \
  -H "Authorization: Bearer kn-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "step-3-7-flash:free",
    "max_tokens": 512,
    "messages": [{"role": "user", "content": "Halo!"}]
  }'

max_tokens di path ini opsional, dan kalau kamu hilangkan, server memakai bawaan 65.536. Letak jawabannya ikut pindah, dari choices[0].message.content ke content[0].text.

Klien Base URL Jawaban ada di
OpenAI SDK / curl chat https://kenari.id/v1 choices[0].message.content
Claude Code https://kenari.id diurus tool-nya
SDK Anthropic (seperti di docs) https://kenari.id/v1 content[0].text

Setelan per editor, termasuk Codex dan OpenCode, ada di arahkan editor kamu ke kenari.

Kalau gagal, baca statusnya

Gejala Artinya Perbaikan
401 invalid api key kn-... salah, terpotong, atau belum di-export Buat key baru, salin utuh. Body 401 teks polos, bukan JSON
model_not_found Id salah ketik Tulis persis step-3-7-flash:free, atau ambil id dari GET /v1/models
402 insufficient_balance Model berbayar, saldo Rp 0 Ganti ke :free, atau isi saldonya dulu
405 Base URL dobel /v1 atau hilang /v1 OpenAI: tepat https://kenari.id/v1. Claude Code: https://kenari.id
429 Limit jalur gratis (per menit atau harian) Tunggu, baca header Retry-After. Detail di errors

GET https://kenari.id/v1/models publik dan nggak butuh key, jadi pakai itu untuk memastikan id-nya masih ada sebelum kamu tempel ke kode. Katalognya berubah, jadi jangan di-hardcode.

Dari kode, bukan cuma curl

Base URL https://kenari.id/v1 tinggal menggantikan https://api.openai.com/v1, dan sisa kodemu nggak perlu disentuh.

from openai import OpenAI

client = OpenAI(base_url="https://kenari.id/v1", api_key="kn-...")
r = client.chat.completions.create(
    model="step-3-7-flash:free",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(r.choices[0].message.content)
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://kenari.id/v1", apiKey: "kn-..." });
const res = await client.chat.completions.create({
  model: "step-3-7-flash:free",
  messages: [{ role: "user", content: "Hello!" }],
});
console.log(res.choices[0].message.content);

Endpoint /v1 mengirim header CORS, jadi fetch langsung dari browser memang jalan tanpa proxy. Jalan, tapi key kn- kamu sekalian terpampang di devtools buat siapa pun yang membuka halamanmu. Untuk prototipe lokal masih aman. Sebelum aplikasinya dibuka orang lain, pindahkan pemanggilannya ke server dan simpan key-nya di sana.

Setelah curl pertama

Field messages menerima peran system, user, assistant, dan tool, tapi untuk sekarang satu user sudah cukup. Buat streaming, tambahkan "stream": true, dan SDK resmi yang bakal mengurai SSE-nya.

Pindah ke model berbayar caranya menghapus akhiran :free, karena id berbayar untuk keluarga yang sama biasanya tanpa akhiran, misalnya step-3-7-flash. Cara mengisi saldonya ada di bayar token pakai QRIS.

Di dashboard ada juga menu Routes (di dokumen Indonesia namanya Rute). Rute itu daftar model bernama yang dicoba berurutan, dan nama rute itulah yang kamu kirim di field model, bukan id model, misalnya opus-hemat. Ada satu rute bawaan, kenari-free, yang mengarah ke model gratis dan nggak bisa diedit.

Satu hal yang mungkin kamu sadari dari respons tadi: nggak ada satu pun nama provider di situ. Itu memang disengaja. Kamu bicara ke gateway, routing memilih jalurnya, dan nama upstream nggak kami bocorkan, termasuk lewat pesan error. Untuk request pertama, yang penting cuma satu key dan satu jawaban.