Docs
Keep the OpenAI or Anthropic SDK you already use, and send your first request in a few minutes.
Quickstart
Create a key, set two environment variables, then send a request.
Point your OpenAI SDK at http://127.0.0.1:3001/v1, or your Anthropic SDK at http://127.0.0.1:3001, and use your yourbrand key. Requests, responses and streams keep the shapes your code already expects. A field we don't support gets a 400 rather than being dropped. Fine-tuning, batch, files and assistants aren't offered.
- 1Create a key
Register with a username, buy $5 slips and redeem them for credit, then create a key in the dashboard.
- 2Set the base URL
export YOURBRAND_BASE_URL=http://127.0.0.1:3001/v1 export YOURBRAND_KEY=sk_...
- 3Send a request
curl $YOURBRAND_BASE_URL/chat/completions \ -H "Authorization: Bearer $YOURBRAND_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"qwen3.8-27b-abliterated","messages":[{"role":"user","content":"hello"}]}'
import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.YOURBRAND_KEY, baseURL: process.env.YOURBRAND_BASE_URL, }); const res = await client.chat.completions.create({ model: "qwen3.8-27b-abliterated", messages: [{ role: "user", content: "hello" }], }); console.log(res.choices[0].message.content);
import os from openai import OpenAI client = OpenAI( api_key=os.environ["YOURBRAND_KEY"], base_url=os.environ["YOURBRAND_BASE_URL"], ) res = client.chat.completions.create( model="qwen3.8-27b-abliterated", messages=[{"role": "user", "content": "hello"}], ) print(res.choices[0].message.content)
using System.Net.Http.Json; var key = Environment.GetEnvironmentVariable("YOURBRAND_KEY"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", $"Bearer {key}"); var res = await http.PostAsJsonAsync("http://127.0.0.1:3001/v1/chat/completions", new { model = "qwen3.8-27b-abliterated", messages = new[] { new { role = "user", content = "hello" } }, }); Console.WriteLine(await res.Content.ReadAsStringAsync());
#include <cpr/cpr.h> #include <cstdlib> #include <iostream> int main() { std::string key = std::getenv("YOURBRAND_KEY"); auto res = cpr::Post( cpr::Url{"http://127.0.0.1:3001/v1/chat/completions"}, cpr::Header{{"Authorization", "Bearer " + key}, {"Content-Type", "application/json"}}, cpr::Body{R"({ "model": "qwen3.8-27b-abliterated", "messages": [{"role":"user","content":"hello"}] })"}); std::cout << res.text << "\n"; }
require "json" require "net/http" res = Net::HTTP.post( URI("http://127.0.0.1:3001/v1/chat/completions"), { model: "qwen3.8-27b-abliterated", messages: [{ role: "user", content: "hello" }], }.to_json, "Authorization" => "Bearer #{ENV['YOURBRAND_KEY']}", "Content-Type" => "application/json", ) puts res.body
package main import ( "fmt" "io" "net/http" "os" "strings" ) func main() { body := `{ "model": "qwen3.8-27b-abliterated", "messages": [{"role":"user","content":"hello"}] }` req, _ := http.NewRequest("POST", "http://127.0.0.1:3001/v1/chat/completions", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("YOURBRAND_KEY")) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() out, _ := io.ReadAll(res.Body) fmt.Println(string(out)) }
Agent setup
Any agent that accepts an OpenAI base URL works. These are the settings for the common ones. OpenCode reads its settings from ~/.config/opencode/opencode.json.
export OPENAI_API_BASE=http://127.0.0.1:3001/v1 export OPENAI_API_KEY=$YOURBRAND_KEY aider --model openai/qwen3.8-27b-abliterated
{
"provider": {
"yourbrand": {
"npm": "@ai-sdk/openai-compatible",
"options": { "baseURL": "http://127.0.0.1:3001/v1", "apiKey": "{env:YOURBRAND_KEY}" },
"models": { "qwen3.8-27b-abliterated": {} }
}
}
}
# ~/.continue/config.yaml
models:
- name: qwen3.8-27b-abliterated
provider: openai
model: qwen3.8-27b-abliterated
apiBase: http://127.0.0.1:3001/v1
apiKey: ${{ secrets.YOURBRAND_KEY }}
Streaming
Set stream: true and the reply arrives as server-sent events while the model writes it. The SDKs handle the parsing; this is what they do underneath, for when you read the stream yourself.
curl -N $YOURBRAND_BASE_URL/chat/completions \ -H "Authorization: Bearer $YOURBRAND_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"qwen3.8-27b-abliterated","stream":true,"messages":[{"role":"user","content":"hello"}]}'
const stream = await client.chat.completions.create({ model: "qwen3.8-27b-abliterated", stream: true, messages: [{ role: "user", content: "hello" }], }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); if (chunk.usage) console.error("\ntokens:", chunk.usage.total_tokens); }
stream = client.chat.completions.create( model="qwen3.8-27b-abliterated", stream=True, messages=[{"role": "user", "content": "hello"}], ) for chunk in stream: if chunk.choices: print(chunk.choices[0].delta.content or "", end="", flush=True) if chunk.usage: print("\ntokens:", chunk.usage.total_tokens)
using System.Net.Http.Json; using System.Text.Json; var baseUrl = Environment.GetEnvironmentVariable("YOURBRAND_BASE_URL"); var key = Environment.GetEnvironmentVariable("YOURBRAND_KEY"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", $"Bearer {key}"); var req = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/chat/completions") { Content = JsonContent.Create(new { model = "qwen3.8-27b-abliterated", stream = true, messages = new[] { new { role = "user", content = "hello" } }, }), }; using var res = await http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead); using var reader = new StreamReader(await res.Content.ReadAsStreamAsync()); while (await reader.ReadLineAsync() is { } line) { if (!line.StartsWith("data: {")) continue; var choices = JsonDocument.Parse(line[6..]).RootElement.GetProperty("choices"); if (choices.GetArrayLength() > 0 && choices[0].GetProperty("delta").TryGetProperty("content", out var text)) Console.Write(text.GetString()); }
#include <cpr/cpr.h> #include <nlohmann/json.hpp> #include <cstdlib> #include <iostream> int main() { std::string base = std::getenv("YOURBRAND_BASE_URL"); std::string key = std::getenv("YOURBRAND_KEY"); std::string buffer; cpr::Post( cpr::Url{base + "/chat/completions"}, cpr::Header{ {"Authorization", "Bearer " + key}, {"Content-Type", "application/json"} }, cpr::Body{R"({"model": "qwen3.8-27b-abliterated", "stream": true, "messages": [{"role": "user", "content": "hello"}]})"}, cpr::WriteCallback{[&](std::string_view data, intptr_t) { buffer.append(data); for (size_t end; (end = buffer.find('\n')) != std::string::npos; buffer.erase(0, end + 1)) { std::string line = buffer.substr(0, end); if (line.rfind("data: {", 0) != 0) continue; auto chunk = nlohmann::json::parse(line.substr(6)); if (!chunk["choices"].empty()) std::cout << chunk["choices"][0]["delta"].value("content", "") << std::flush; } return true; }}); }
require "json" require "net/http" uri = URI("#{ENV['YOURBRAND_BASE_URL']}/chat/completions") body = { model: "qwen3.8-27b-abliterated", stream: true, messages: [{ role: "user", content: "hello" }] } Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{ENV['YOURBRAND_KEY']}", "Content-Type" => "application/json") req.body = body.to_json http.request(req) do |res| buffer = +"" res.read_body do |data| buffer << data while (line = buffer.slice!(/.*\n/)) next unless line.start_with?("data: {") text = JSON.parse(line[6..]).dig("choices", 0, "delta", "content") print text if text end end end end
package main import ( "bufio" "encoding/json" "fmt" "net/http" "os" "strings" ) func main() { body := `{"model": "qwen3.8-27b-abliterated", "stream": true, "messages": [{"role": "user", "content": "hello"}]}` req, _ := http.NewRequest("POST", os.Getenv("YOURBRAND_BASE_URL")+"/chat/completions", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("YOURBRAND_KEY")) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() scanner := bufio.NewScanner(res.Body) for scanner.Scan() { data, ok := strings.CutPrefix(scanner.Text(), "data: ") if !ok || data == "[DONE]" { continue } var chunk struct { Choices []struct { Delta struct{ Content string } `json:"delta"` } `json:"choices"` } if json.Unmarshal([]byte(data), &chunk) == nil && len(chunk.Choices) > 0 { fmt.Print(chunk.Choices[0].Delta.Content) } } }
- Each event is a
data:line holding onechat.completion.chunk. - The last chunk carries
usage, so you can count a stream's tokens without a second request. - The stream ends with
data: [DONE]. Closing the connection early stops generation.
Event-by-event shapes, including the Anthropic stream, are in the API reference.
Handling errors
Three errors are worth retrying: 429 when you're over your plan's parallel limit, with a Retry-After header in seconds; 500 when something fails on our side; and 503 when a model is briefly not serving. Everything else needs a change to the request, and retrying won't help.
curl --retry 5 --retry-all-errors --retry-max-time 60 \ $YOURBRAND_BASE_URL/chat/completions \ -H "Authorization: Bearer $YOURBRAND_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"qwen3.8-27b-abliterated","messages":[{"role":"user","content":"hello"}]}'
// The OpenAI SDK already retries 429 and 5xx with backoff and honours Retry-After. const client = new OpenAI({ apiKey: process.env.YOURBRAND_KEY, baseURL: process.env.YOURBRAND_BASE_URL, maxRetries: 5, });
# The OpenAI SDK already retries 429 and 5xx with backoff and honours Retry-After. client = OpenAI( api_key=os.environ["YOURBRAND_KEY"], base_url=os.environ["YOURBRAND_BASE_URL"], max_retries=5, )
using System.Net.Http.Json; var baseUrl = Environment.GetEnvironmentVariable("YOURBRAND_BASE_URL"); var key = Environment.GetEnvironmentVariable("YOURBRAND_KEY"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", $"Bearer {key}"); async Task<HttpResponseMessage> WithRetry(Func<Task<HttpResponseMessage>> call, int tries = 5) { for (var attempt = 0; ; attempt++) { var res = await call(); var retryable = (int)res.StatusCode is 429 or 500 or 503; if (!retryable || attempt == tries - 1) return res; var wait = res.Headers.RetryAfter?.Delta ?? TimeSpan.FromMilliseconds((250 << attempt) + Random.Shared.Next(100)); await Task.Delay(wait); } } var res = await WithRetry(() => http.PostAsJsonAsync($"{baseUrl}/chat/completions", new { model = "qwen3.8-27b-abliterated", messages = new[] { new { role = "user", content = "hello" } }, }));
#include <cpr/cpr.h> #include <chrono> #include <cstdlib> #include <functional> #include <thread> cpr::Response with_retry(const std::function<cpr::Response()>& call, int tries = 5) { for (int attempt = 0;; attempt++) { cpr::Response res = call(); bool retryable = res.status_code == 429 || res.status_code == 500 || res.status_code == 503; if (!retryable || attempt == tries - 1) return res; std::string after = res.header["retry-after"]; int wait_ms = !after.empty() ? std::stoi(after) * 1000 : (250 << attempt) + std::rand() % 100; std::this_thread::sleep_for(std::chrono::milliseconds(wait_ms)); } } int main() { std::string base = std::getenv("YOURBRAND_BASE_URL"); std::string key = std::getenv("YOURBRAND_KEY"); auto res = with_retry([&] { return cpr::Post(cpr::Url{base + "/chat/completions"}, cpr::Header{ {"Authorization", "Bearer " + key}, {"Content-Type", "application/json"} }, cpr::Body{R"({"model": "qwen3.8-27b-abliterated", "messages": [{"role": "user", "content": "hello"}]})"}); }); }
require "json" require "net/http" def with_retry(tries: 5) tries.times do |attempt| res = yield retryable = %w[429 500 503].include?(res.code) return res if !retryable || attempt == tries - 1 sleep(res["retry-after"]&.to_f || (2**attempt * 0.25) + (rand * 0.1)) end end uri = URI("#{ENV['YOURBRAND_BASE_URL']}/chat/completions") res = with_retry do Net::HTTP.post(uri, { model: "qwen3.8-27b-abliterated", messages: [{ role: "user", content: "hello" }] }.to_json, "Authorization" => "Bearer #{ENV['YOURBRAND_KEY']}", "Content-Type" => "application/json") end
package main import ( "math/rand/v2" "net/http" "os" "strconv" "strings" "time" ) func withRetry(call func() (*http.Response, error), tries int) (*http.Response, error) { for attempt := 0; ; attempt++ { res, err := call() if err != nil { return nil, err } retryable := res.StatusCode == 429 || res.StatusCode == 500 || res.StatusCode == 503 if !retryable || attempt == tries-1 { return res, nil } res.Body.Close() wait := time.Duration(250<<attempt+rand.IntN(100)) * time.Millisecond if after, err := strconv.Atoi(res.Header.Get("Retry-After")); err == nil { wait = time.Duration(after) * time.Second } time.Sleep(wait) } } func main() { body := `{"model": "qwen3.8-27b-abliterated", "messages": [{"role": "user", "content": "hello"}]}` res, err := withRetry(func() (*http.Response, error) { req, _ := http.NewRequest("POST", os.Getenv("YOURBRAND_BASE_URL")+"/chat/completions", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("YOURBRAND_KEY")) req.Header.Set("Content-Type", "application/json") return http.DefaultClient.Do(req) }, 5) if err != nil { panic(err) } defer res.Body.Close() }
Every status code and both error shapes are listed under Errors; plan limits are under Rate limits.