Documentation

Thalera speaks the OpenAI chat completions API. If your code already talks to OpenAI, it already talks to Thalera.

Quickstart

Create a key, then change the base URL your client points at. Nothing else in your application needs to change.

.env
# Before
OPENAI_BASE_URL=https://api.openai.com/v1

# After
OPENAI_BASE_URL=https://api.thalera.dev/v1
THALERA_API_KEY=thl_live_...

First request

Use the virtual model thalera-auto to let routing choose. You can also pass a real model id to pin it while keeping failover.

curl
curl https://api.thalera.dev/v1/chat/completions \
  -H "Authorization: Bearer $THALERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "thalera-auto",
    "messages": [
      { "role": "user", "content": "Summarize this ticket in one line." }
    ]
  }'

The response is a standard completion plus a thalera block describing the decision.

json
{
  "id": "cmpl_9xQ...",
  "model": "gemini-flash-lite",
  "choices": [{ "message": { "role": "assistant", "content": "..." } }],
  "thalera": {
    "policy": "cost_optimized",
    "reason": "low_complexity",
    "cache": "miss",
    "cost_usd": 0.000042,
    "latency_ms": 310
  }
}

Routing policies

A policy is attached to your key by default. Override it per request with headers when a specific call needs different behaviour.

PolicyOptimizes for
cost_optimizedCheapest model above the quality floor
latency_firstFastest healthy model within budget
quality_firstFrontier tier, cache still active
pinnedA named model, with failover
curl
# Per-request override
curl https://api.thalera.dev/v1/chat/completions \
  -H "Authorization: Bearer $THALERA_API_KEY" \
  -H "X-Thalera-Policy: latency_first" \
  -H "X-Thalera-Quality-Floor: 0.95" \
  -d '{ "model": "thalera-auto", "messages": [...] }'

Streaming

Streaming works exactly as upstream. If a provider fails before the first token, Thalera re-routes transparently; after the first token it fails over to a retry.

python
stream = client.chat.completions.create(
    model="thalera-auto",
    messages=[{"role": "user", "content": prompt}],
    stream=True,
)

for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")

# Routing metadata arrives in the final chunk as
# chunk.thalera — including the model actually used.

Caching

The semantic cache is on by default for cost_optimized. Namespace it per tenant so users never see each other's answers.

python
client.chat.completions.create(
    model="thalera-auto",
    messages=messages,
    extra_headers={
        "X-Thalera-Cache": "on",        # on | off | refresh
        "X-Thalera-Cache-Namespace": tenant_id,
        "X-Thalera-Cache-TTL": "3600",
    },
)

Error codes

StatusCodeMeaning
400invalid_requestMalformed body or unknown policy name.
401invalid_keyMissing or revoked Thalera API key.
402budget_exceededSpend cap for this key was reached.
409no_eligible_modelNo model met the quality floor and latency budget.
429rate_limitedPer-key concurrency limit hit. Retry with backoff.
503all_providers_degradedEvery candidate provider failed health checks.

Need something not covered here?

Batch endpoints, embeddings routing, self-hosted classifiers and private deployments are all supported.

Contact us