API documentation
Two drop-in endpoint families, three model names, honest caching. Base URL: https://api.bhaskaralabs.com
Quick start
Get a key
Sign in with GitHub → dashboard → New key. Shown once, stored hashed; revoke anytime.
Point one env var
Every mainstream agent accepts a custom OpenAI- or Anthropic-compatible base URL:
export BHASKARA_API_KEY="sk-bhaskara-…"
export BHASKARA_BASE_URL="https://api.bhaskaralabs.com/v1"Call it like any OpenAI API
curl -s $BHASKARA_BASE_URL/chat/completions \
-H "Authorization: Bearer $BHASKARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "theta",
"messages": [{ "role": "user", "content": "Rust: borrow checker, one paragraph." }]
}'Models you can name
| Name | What it is | Metered as |
|---|---|---|
glm-5.3 | GLM-5.3 family — router picks full vs flash variant per session, disclosed in headers | frontier token quota |
qwen-3.8 | Qwen3.8 family — same session-sticky routing | frontier token quota |
theta | flat per-request endpoint — cheapest backend that can handle the turn | requests (5h window + monthly) |
Flash variants exist upstream but are selected by our session router — you address the family name, never a silently substituted different family.
Endpoints
POST /v1/chat/completions
OpenAI-compatible. Supports messages, stream, temperature, max_tokens, and standard tools / tool_choice. Streaming follows OpenAI SSE (data: chunks, [DONE], usage in the final chunk when requested).
curl -s $BHASKARA_BASE_URL/chat/completions \
-H "Authorization: Bearer $BHASKARA_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"glm-5.3","messages":[{"role":"user","content":"explain cache-aware prompt prefixes"}]}'POST /v1/messages
Anthropic-compatible: system, messages[] with content blocks, anthropic-version, x-api-key auth, SSE event shapes Claude Code expects.
Any OpenAI SDK works unchanged
from openai import OpenAI
client = OpenAI(
base_url="https://api.bhaskaralabs.com/v1",
api_key="sk-bhaskara-…",
)
r = client.chat.completions.create(
model="theta",
messages=[{"role": "user", "content": "fix my SQL window fn"}],
)
print(r.choices[0].message.content)import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
baseURL: "https://api.bhaskaralabs.com",
apiKey: process.env.BHASKARA_API_KEY,
});
const msg = await anthropic.messages.create({
model: "glm-5.3",
max_tokens: 1024,
messages: [{ role: "user", content: "review this diff" }],
});Terse mode (output compression, opt-in)
Send x-bhaskara-terse: 1 on any request (both endpoints) and replies come back in compressed engineer-speak: no preambles, no restatements, no closing summaries — code, commands, and file paths stay byte-exact. Measured on our routing stack: ~2× fewer output tokens with more usable content per token.
curl -s $BHASKARA_BASE_URL/chat/completions \
-H "Authorization: Bearer $BHASKARA_API_KEY" \
-H "x-bhaskara-terse: 1" \
-H "Content-Type: application/json" \
-d '{"model":"glm-5.3","messages":[…]}'Cache note: the discipline block lives in your system prompt — it changes the prefix exactly once when you enable it, then stays byte-stable for the whole session. Keep the header on every request of a session (agents set headers once anyway). Safety-sensitive replies (warnings, irreversible-action confirmations) are exempted from compression by instruction.
Integrate your coding agent
Verified shapes only — if a tool needs a flag we haven't listed, the tool's docs own it; the three values you always need are base URL, key, model name.
Claude Code
ANTHROPIC_BASE_URL=https://api.bhaskaralabs.com \
ANTHROPIC_AUTH_TOKEN=$BHASKARA_API_KEY \
claude --model glm-5.3Uses the /v1/messages path natively. Sub-agents inherit the base URL.
Codex CLI
~/.codex/config.toml:
model = "glm-5.3"
model_provider = "bhaskara"
[model_providers.bhaskara]
name = "Bhaskara Labs"
base_url = "https://api.bhaskaralabs.com/v1"
env_key = "BHASKARA_API_KEY"
wire_api = "chat"openCode
opencode.json:
{
"provider": {
"bhaskara": {
"npm": "@ai-sdk/openai-compatible",
"options": {
"baseURL": "https://api.bhaskaralabs.com/v1",
"apiKey": "{env:BHASKARA_API_KEY}"
},
"models": {
"glm-5.3": { "name": "GLM-5.3 (routed)" },
"theta": { "name": "Theta" }
}
}
},
"model": "bhaskara/glm-5.3"
}Crush (Charm)
Crush's provider config accepts an OpenAI-compatible override — same three values. If you already ran Crush against Hyper, point the base URL here and swap the key.
pi / omp
Both accept custom OpenAI-compatible providers in their model config (models.json / provider list). Add bhaskara with the base URL above, models glm-5.3, qwen-3.8, theta, and API key via env.
Prompt caching: the rules that earn you money
1 — Stable prefix first. Cache hits are computed on the request prefix. System prompt → tool definitions → long-lived context → then your varying turns. Byte-identical prefix = cache read; one edit mid-context invalidates everything after it.
2 — Append-only sessions. Don't rewrite history, don't prune the middle. Trim from the front at safe boundaries or not at all.
3 — Keep the family stable per session — with one priced exception. Our router locks a session to the model variant chosen at its first request (2-hour idle TTL). Flipping variants mid-session would wipe the provider cache and re-bill your whole prefix — so we don't. One exception: a genuinely hard turn (planning, debugging, architecture) in a flash-locked session can trigger a one-time upgrade to the full model — but only when the cache-wipe penalty (your prefix re-billed at full-model rates) stays under a strict budget and your weekly full-model share has headroom. Downgrades mid-session never happen.
4 — Watch the headers. x-ratelimit-remaining-* and the dashboard cache column tell you the real hit rate. If it's low, your prefix ordering is the problem, not the pipe.
Quota headers & errors
Every response carries your live quota state (no pricing data in headers):
| x-quota-plan | free | basic | advanced |
| x-quota-monthly-reset | ISO date the token quotas roll over |
| x-ratelimit-{limit,remaining,reset}-tokens | frontier: monthly token dimension; reset = seconds |
| x-ratelimit-{limit,remaining,reset}-requests | theta: the rolling 5-hour window |
| x-quota-output-tokens-remaining | frontier monthly output remainder |
| Retry-After | only on 429 — seconds to wait before the window/month resets |
401 bad/revoked key · 400 unknown model name (the allowed three are above) · 429 quota — with Retry-After, never silent degrade · 502 upstream failure — retryable, our error body never names providers.
Backoff: honour Retry-After first, else exponential with jitter. A flat 429 storm with no Retry-After means you're hammering with a wrong key, not out of quota.
Streaming
"stream": true on /v1/chat/completions gives OpenAI-style SSE; "stream": true on /v1/messages gives Anthropic event types (message_start, content_block_delta, message_stop). Tool-call deltas stream in the same shape the native APIs use, so agent harnesses parse them without adapters.
Trouble? FAQ · billing plans & calculator · terms