OpenClaw
Gateway chat→agen (WhatsApp/Telegram/Slack). Provider openai-completions kustom.
Base URL dan API key diambil dari dasbor RouterKu (menu Integrasi & Kunci API). Pada contoh, ganti BASE_URL_DARI_DASBOR (berakhiran /v1), HOST_DARI_DASBOR (tanpa /v1) dan qdr-... dengan nilai milikmu.
OpenClaw adalah gateway agen AI self-hosted untuk WhatsApp, Telegram, Slack, Discord, dll. Ia mendukung provider OpenAI-compatible, jadi RouterKu cukup didaftarkan sebagai custom provider lewat baseUrl + API key, lalu dipakai dengan format routerku/<model>.
Nilai koneksi
| Base URL | BASE_URL_DARI_DASBOR |
| API Key | qdr-... (buat di menu Kunci API) |
| Model | sonnet-5 / opus-5 |
Langkah-langkah
Pasang OpenClaw
npm install -g openclaw@latest openclaw onboard --install-daemonButuh Node.js 20+.
onboardmemandu setup awal & memasang daemon gateway.Simpan API key RouterKu
Cara paling aman dari error 401: taruh key di blok
envdalam~/.openclaw/openclaw.json(ikut hot-reload, tidak perlu restart):{ env: { ROUTERKU_API_KEY: "qdr-..." }, }Alternatif: file
~/.openclaw/.env. Isi.envhanya dibaca saat Gateway START — kalau kamu ubah.envsaat Gateway sudah jalan, wajib restart, kalau tidak key lama/kosong tetap dipakai dan muncul HTTP 401.echo "ROUTERKU_API_KEY=qdr-..." >> ~/.openclaw/.env openclaw gateway restartDaftarkan RouterKu sebagai custom provider
Edit
~/.openclaw/openclaw.json. Gateway auto-reload begitu file disimpan:{ // JSON5 — boleh pakai komentar & koma di akhir models: { mode: "merge", providers: { routerku: { baseUrl: "BASE_URL_DARI_DASBOR", apiKey: "${ROUTERKU_API_KEY}", api: "openai-completions", models: [ { id: "sonnet-5", name: "Claude Sonnet 5", input: ["text"], contextWindow: 1000000, maxTokens: 8192 }, { id: "opus-5", name: "Claude Opus 5", input: ["text"], contextWindow: 1000000, maxTokens: 8192 }, { id: "haiku-45", name: "Claude Haiku 4.5", input: ["text"], contextWindow: 200000, maxTokens: 8192 }, ], }, }, }, agents: { defaults: { model: { primary: "routerku/sonnet-5", fallbacks: ["routerku/opus-5"], }, models: { "routerku/sonnet-5": { alias: "Sonnet" }, "routerku/opus-5": { alias: "Opus" }, "routerku/haiku-45": { alias: "Haiku" }, }, }, }, }api: "openai-completions"= endpoint/v1/chat/completionsRouterKu.${ROUTERKU_API_KEY}diganti otomatis dari variabel lingkungan (blokenvatau.env). Nama variabel wajib HURUF BESAR — pola yang dikenali hanya[A-Z_][A-Z0-9_]*; huruf kecil TIDAK diganti dan key terkirim mentah sebagai teks${...}→ 401.Kalau masih 401, boleh tulis key langsung (tanpa
${}) diapiKeyuntuk memastikan:apiKey: "qdr-...".Jalankan & buka dashboard
openclaw dashboard # UI kontrol di http://127.0.0.1:18789Cek model kebaca, atau ganti model saat chat dengan
/model.(Opsional) Sambungkan channel
Contoh Telegram — tambahkan di
~/.openclaw/openclaw.json:{ channels: { telegram: { enabled: true, botToken: "123456:ABC-token-dari-BotFather", dmPolicy: "pairing", // pairing | allowlist | open | disabled }, }, }Channel lain (WhatsApp, Slack, Discord, dll) polanya sama di bawah
channels.<provider>.
Verifikasi cepat
openclaw models list # harus muncul routerku/sonnet-5 dll
openclaw doctor # cek config & koneksiKalau muncul 401, pastikan dulu key-nya memang hidup lewat Uji mandiri kunci berikut (harus balas 200):
curl -s -o /dev/null -w "%{http_code}\n" BASE_URL_DARI_DASBOR/chat/completions \
-H "Authorization: Bearer $ROUTERKU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"haiku-45","max_tokens":10,"messages":[{"role":"user","content":"ping"}]}'Hasil 200 = kunci & saldo RouterKu sehat, jadi 401 di OpenClaw murni soal key yang belum terbaca Gateway (lihat bagian Kalau bermasalah).
Model yang disarankan
opus-5 | Terkuat — penalaran mendalam untuk tugas berat, banyak tool, dan agen coding. |
sonnet-5 | Andalan harian (default) — seimbang antara kualitas, kecepatan, dan biaya. |
haiku-45 | Tercepat & termurah — balasan chat ringan, klasifikasi, dan boilerplate. |
Tips
- Format referensi model selalu
provider/model→ di sinirouterku/sonnet-5. - Mau tambah model? Tinggal tambah entri di
models.providers.routerku.models+ alias diagents.defaults.models. - Lewat CLI (tanpa edit file):
openclaw config set agents.defaults.model.primary routerku/sonnet-5. - Prompt caching RouterKu aktif otomatis untuk model Claude → hemat di sesi panjang.
Kalau bermasalah
- Config ditolak / gateway gak mau start → OpenClaw validasi ketat. Jalankan
openclaw doctor --fix. authentication failed, HTTP 401padarouterku/<model>→ hampir selalu key TIDAK sampai ke request, bukan key mati. Urutan cek: (1)openclaw gateway restartsetelah mengubah~/.openclaw/.env(file itu hanya dibaca saat start); (2) nama variabel harus HURUF BESAR (ROUTERKU_API_KEY); (3) jangan ada spasi/kutip/baris kosong di.env(ROUTERKU_API_KEY=qdr-...); (4) coba tulis key apa adanya diapiKeyuntuk memastikan.- Uji key-nya langsung dengan
curldi bawah (bagian Uji endpoint dari kode). Kalau curl 200 tapi OpenClaw 401 → masalah di config OpenClaw, bukan di RouterKu. - Benar-benar 401 dari kami → kunci dicabut/salah ketik. Terbitkan kunci baru di menu Kunci API. 402 = saldo habis, isi di Isi saldo.
- Tidak konek → pastikan
baseUrlberakhiran/v1dan variabelROUTERKU_API_KEYbenar-benar terbaca Gateway. - Model ditolak → pakai ID persis (mis.
sonnet-5) dan rujuk lengkaprouterku/sonnet-5.
Uji endpoint dari kode (opsional)
curl BASE_URL_DARI_DASBOR/chat/completions \
-H "Authorization: Bearer qdr-..." \
-H "Content-Type: application/json" \
-d '{"model":"sonnet-5","messages":[{"role":"user","content":"Halo!"}]}'from openai import OpenAI
client = OpenAI(api_key="qdr-...", base_url="BASE_URL_DARI_DASBOR")
resp = client.chat.completions.create(
model="sonnet-5",
messages=[{"role": "user", "content": "Halo!"}],
)
print(resp.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI({ apiKey: "qdr-...", baseURL: "BASE_URL_DARI_DASBOR" });
const resp = await client.chat.completions.create({
model: "sonnet-5",
messages: [{ role: "user", content: "Halo!" }],
});
console.log(resp.choices[0].message.content);const res = await fetch("BASE_URL_DARI_DASBOR/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer qdr-...",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "sonnet-5",
messages: [{ role: "user", content: "Halo!" }],
}),
});
const data = await res.json();
console.log(data.choices[0].message.content);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]any{
"model": "sonnet-5",
"messages": []map[string]string{{"role": "user", "content": "Halo!"}},
})
req, _ := http.NewRequest("POST", "BASE_URL_DARI_DASBOR/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer qdr-...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}<?php
$ch = curl_init("BASE_URL_DARI_DASBOR/chat/completions");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer qdr-...", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"model" => "sonnet-5",
"messages" => [["role" => "user", "content" => "Halo!"]],
]),
]);
$res = json_decode(curl_exec($ch), true);
echo $res["choices"][0]["message"]["content"];require "net/http"
require "json"
uri = URI("BASE_URL_DARI_DASBOR/chat/completions")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer qdr-..."
req["Content-Type"] = "application/json"
req.body = { model: "sonnet-5", messages: [{ role: "user", content: "Halo!" }] }.to_json
res = http.request(req)
puts JSON.parse(res.body)["choices"][0]["message"]["content"]import java.net.URI;
import java.net.http.*;
var client = HttpClient.newHttpClient();
var body = "{\"model\":\"sonnet-5\",\"messages\":[{\"role\":\"user\",\"content\":\"Halo!\"}]}";
var req = HttpRequest.newBuilder(URI.create("BASE_URL_DARI_DASBOR/chat/completions"))
.header("Authorization", "Bearer qdr-...")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());using System.Net.Http;
using System.Text;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer qdr-...");
var json = "{\"model\":\"sonnet-5\",\"messages\":[{\"role\":\"user\",\"content\":\"Halo!\"}]}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await client.PostAsync("BASE_URL_DARI_DASBOR/chat/completions", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());// Cargo.toml: reqwest = { version = "0.12", features = ["json", "blocking"] }
use serde_json::json;
fn main() {
let res = reqwest::blocking::Client::new()
.post("BASE_URL_DARI_DASBOR/chat/completions")
.header("Authorization", "Bearer qdr-...")
.json(&json!({ "model": "sonnet-5", "messages": [{ "role": "user", "content": "Halo!" }] }))
.send().unwrap().text().unwrap();
println!("{res}");
}Siap mulai pakai AI tanpa ribet?
Daftar gratis, isi saldo mulai Rp20.000 lewat QRIS, dan langsung tancap gas di editor atau aplikasimu.