API documentation

Two drop-in endpoint families, three model names, honest caching. Base URL: https://api.bhaskaralabs.com

Quick start

1

Get a key

Sign in with GitHub → dashboard → New key. Shown once, stored hashed; revoke anytime.

2

Point one env var

Every mainstream agent accepts a custom OpenAI- or Anthropic-compatible base URL:

bash
export BHASKARA_API_KEY="sk-bhaskara-…"
export BHASKARA_BASE_URL="https://api.bhaskaralabs.com/v1"
3

Call it like any OpenAI API

bash
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

NameWhat it isMetered as
glm-5.3GLM-5.3 family — router picks full vs flash variant per session, disclosed in headersfrontier token quota
qwen-3.8Qwen3.8 family — same session-sticky routingfrontier token quota
thetaflat per-request endpoint — cheapest backend that can handle the turnrequests (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).

bash
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

python
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)
typescript
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.

bash
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

bash
ANTHROPIC_BASE_URL=https://api.bhaskaralabs.com \
ANTHROPIC_AUTH_TOKEN=$BHASKARA_API_KEY \
claude --model glm-5.3

Uses the /v1/messages path natively. Sub-agents inherit the base URL.

Codex CLI

~/.codex/config.toml:

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:

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-planfree | basic | advanced
x-quota-monthly-resetISO date the token quotas roll over
x-ratelimit-{limit,remaining,reset}-tokensfrontier: monthly token dimension; reset = seconds
x-ratelimit-{limit,remaining,reset}-requeststheta: the rolling 5-hour window
x-quota-output-tokens-remainingfrontier monthly output remainder
Retry-Afteronly 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