Skip to content

Examples

Beta. Updated 2026-10-29.

The example checker runs every example on this page against the live API.

export CITADEL_API_KEY=hlk_...
pip install openai   # Python examples; JavaScript examples require Node 18 or later

curl

Not streamed
curl https://citadel.albastov.com/v1/chat/completions \
  -H "Authorization: Bearer $CITADEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "qwen3.8-27b", "max_tokens": 100,
       "messages": [{"role": "user", "content": "Give me three names for a grey cat."}],
       "chat_template_kwargs": {"enable_thinking": false}}'
Streamed
curl -N https://citadel.albastov.com/v1/chat/completions \
  -H "Authorization: Bearer $CITADEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "qwen3.8-27b", "stream": true, "max_tokens": 100,
       "messages": [{"role": "user", "content": "Give me three names for a grey cat."}],
       "chat_template_kwargs": {"enable_thinking": false}}'

Python, OpenAI SDK

Two turns of one conversation:

import os
from openai import OpenAI

client = OpenAI(base_url="https://citadel.albastov.com/v1", api_key=os.environ["CITADEL_API_KEY"])
no_thinking = {"chat_template_kwargs": {"enable_thinking": False}}

history = [{"role": "user", "content": "Explain prefix caching in two sentences."}]
reply = client.chat.completions.create(model="qwen3.8-27b", messages=history, max_tokens=120, extra_body=no_thinking)
print(reply.choices[0].message.content)

history += [{"role": "assistant", "content": reply.choices[0].message.content},
            {"role": "user", "content": "Now in one."}]
reply = client.chat.completions.create(model="qwen3.8-27b", messages=history, max_tokens=60, extra_body=no_thinking)
print(reply.choices[0].message.content)
print(reply.usage)

JavaScript, streamed

const res = await fetch("https://citadel.albastov.com/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CITADEL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "qwen3.8-27b",
    stream: true,
    max_tokens: 100,
    messages: [{ role: "user", content: "Give me three names for a grey cat." }],
    chat_template_kwargs: { enable_thinking: false },
  }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);

const decoder = new TextDecoder();
let buffer = "";
for await (const bytes of res.body) {
  buffer += decoder.decode(bytes, { stream: true });
  let end;
  while ((end = buffer.indexOf("\n\n")) >= 0) {
    const event = buffer.slice(0, end);
    buffer = buffer.slice(end + 2);
    const data = event.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("");
    if (!data || data === "[DONE]") continue;
    const chunk = JSON.parse(data);
    if (chunk.error) throw new Error(`${chunk.error.type}: ${chunk.error.message}`);
    process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
  }
}
console.log();

30 parallel conversations

Thirty conversations with asyncio, then a second turn on all thirty. The script prints time, output tokens, throughput and cached prompt tokens per turn. The script makes 60 calls; each call counts toward the daily limit.

import asyncio, os, time
from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="https://citadel.albastov.com/v1", api_key=os.environ["CITADEL_API_KEY"])
topics = ["rivers", "volcanoes", "glaciers", "deserts", "coral reefs", "tides", "comets", "auroras", "monsoons",
          "caves", "geysers", "fjords", "dunes", "lightning", "tsunamis", "meteors", "lagoons", "canyons", "atolls",
          "eclipses", "hurricanes", "permafrost", "savannas", "estuaries", "nebulae", "pulsars", "quasars", "moons",
          "asteroids", "solar wind"]
conversations = [[{"role": "user", "content": f"In two sentences, what are {t}?"}] for t in topics]

async def turn(history):
    r = await client.chat.completions.create(
        model="qwen3.8-27b", messages=history, max_tokens=120,
        extra_body={"chat_template_kwargs": {"enable_thinking": False}},
    )
    history.append({"role": "assistant", "content": r.choices[0].message.content})
    return r.usage

async def main():
    for label, ask in (("turn 1", None), ("turn 2", "Now say it in one sentence.")):
        if ask:
            for history in conversations:
                history.append({"role": "user", "content": ask})
        start = time.monotonic()
        usages = await asyncio.gather(*(turn(h) for h in conversations))
        secs = time.monotonic() - start
        out = sum(u.completion_tokens for u in usages)
        cached = sum((u.prompt_tokens_details.cached_tokens or 0) for u in usages if u.prompt_tokens_details)
        print(f"{label}: {len(usages)} answers in {secs:.1f} s, {out} tokens out, {out / secs:.0f} tok/s total, "
              f"{cached} prompt tokens from cache")
    print(conversations[0][-1]["content"])

asyncio.run(main())

503 retries

model_loading, queued, gpu-busy and device_busy return 503 with Retry-After. Wait Retry-After seconds and send the same request again. Do not retry 429 daily_limit: the limit resets at 00:00 UTC.

Python

import os, time
from openai import OpenAI, APIStatusError

client = OpenAI(base_url="https://citadel.albastov.com/v1", api_key=os.environ["CITADEL_API_KEY"], max_retries=0)
RETRY = {"model_loading", "queued", "gpu-busy", "device_busy"}

def create(deadline_s=900, **kwargs):
    start = time.monotonic()
    while True:
        try:
            return client.chat.completions.create(**kwargs)
        except APIStatusError as e:
            code = e.body.get("code") if isinstance(e.body, dict) else None
            wait = int(e.response.headers.get("Retry-After", "10"))
            if e.status_code != 503 or code not in RETRY or time.monotonic() - start + wait > deadline_s:
                raise
            print(f"503 {code}, retrying in {wait} s")
            time.sleep(wait)

reply = create(model="qwen3.8-27b", max_tokens=60,
               messages=[{"role": "user", "content": "One sentence on patience."}],
               extra_body={"chat_template_kwargs": {"enable_thinking": False}})
print(reply.choices[0].message.content)

JavaScript

const RETRY = new Set(["model_loading", "queued", "gpu-busy", "device_busy"]);

async function create(body, deadlineMs = 900_000) {
  const start = Date.now();
  for (;;) {
    const res = await fetch("https://citadel.albastov.com/v1/chat/completions", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.CITADEL_API_KEY}`,
        "Content-Type": "application/json",
        "X-LLM-Wait": "0", // 503 at once; the loop waits
      },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();
    const err = (await res.json().catch(() => ({}))).error ?? {};
    const waitS = Number(res.headers.get("Retry-After") ?? 10);
    if (res.status !== 503 || !RETRY.has(err.code) || Date.now() - start + waitS * 1000 > deadlineMs) {
      throw new Error(`${res.status} ${err.code ?? ""} ${err.message ?? ""}`);
    }
    console.log(`503 ${err.code}, retrying in ${waitS} s`);
    await new Promise((r) => setTimeout(r, waitS * 1000));
  }
}

const reply = await create({
  model: "qwen3.8-27b",
  max_tokens: 60,
  messages: [{ role: "user", content: "One sentence on patience." }],
  chat_template_kwargs: { enable_thinking: false },
});
console.log(reply.choices[0].message.content);