Aura
Start here. Learn how to send requests, pick models, read responses, and fix common errors. The API is OpenAI-compatible — most SDKs work after you set the base URL and use namespaced model names.
Overview
AURA exposes an OpenAI-compatible REST API for chat, completions, embeddings, decision models, and related inference. Request and response bodies match what you already know from OpenAI; routing and policy are where AURA adds behaviour.
Use this guide for integration. Use the console for day-to-day operations:
- Keys — create and manage API keys
- Overview — spend and usage
- Traces — inspect individual requests
Prerequisites
Before your first call, gather:
- Base URL — the HTTPS origin of your AURA deployment (shown in examples as
AURA_BASE) - API key — create one on the Keys page, or ask your administrator. Keys start with
sk-and are only shown once at creation.
Set these shell variables for the curl examples:
export AURA_BASE="https://aura.shyftops.io/api"
export AURA_KEY="sk-..."Quick start
Send a chat completion with automatic model selection (auto/mid):
curl "$AURA_BASE/v1/chat/completions" \
-H "Authorization: Bearer $AURA_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto/mid",
"messages": [{"role": "user", "content": "Name three prime numbers."}]
}'The response is an ordinary OpenAI chat completion, with the answer in choices[0].message.content.
Namespaced models
Every model value must be namespaced. Bare provider model ids (as OpenAI accepts) are rejected with a 400:
| model value | Result |
|---|---|
gpt-4o | 400 — invalid model parameter |
openai/gpt-4o | Routes to that exact model |
auto/mid | AURA picks a model for you |
anthropic/auto/quality | AURA picks, restricted to Anthropic |
decision/jev | A decision model (typed answers, not text). See Decision models |
This is the single change most existing integrations need. Porting an OpenAI client usually means changing the base URL and prefixing every model name — nothing else.
Model names are lowercased before matching, so casing in your request does not matter.
Endpoints
Authenticate every request with a bearer token in the Authorization header.
| Endpoint | Purpose |
|---|---|
POST /v1/chat/completions | The primary endpoint. Chat-style inference |
POST /v1/completions | Older text-completion style |
POST /v1/embeddings | Embedding vectors |
GET /v1/models | Models this key may call, filtered by policy |
GET /v1/models/{model} | One model, including structured-output support |
GET /v1/key | Inspect the calling key: policy, spend, remaining quota |
POST /v1/compress | Prompt compression |
POST /v1/decision | Decision models: typed questions → calibrated answers |
POST /v1/systemone | TypeSafe-compatible alias of /v1/decision |
POST /v1/classify/auto | Show which model routing would pick, without calling it |
POST /v1/classify/auto/{tier} | The same, with the tier fixed |
GET /v1/key answers what this key is allowed to do and how much of its allowance is left. It reads counters without consuming any.
Credentials
| Credential | Reaches | Typical holder |
|---|---|---|
Managed API key (sk-...) | /v1 inference | An application |
| Administrator key | /v1 and /admin | A provisioning script |
| Console session | /mgmt | A person in a browser |
Managed keys carry policy — permitted models, rate limits, spend caps. The other two do not.
Models & routing
The model field tells AURA what to run. Pin an exact model, or use auto and a tier so AURA can choose, fail over, and respect policy.
The grammar
| Form | Meaning |
|---|---|
provider/model_id | Use exactly this model |
auto | AURA picks, all providers, default tier |
auto/tier | AURA picks, all providers, named tier |
provider/auto | AURA picks, one provider, default tier |
provider/auto/tier | AURA picks, one provider, named tier |
Only the first slash separates provider from model, so identifiers containing slashes work as expected.
An unrecognised tier after auto/ returns 400 rather than falling back silently.
Naming a model directly
{ "model": "anthropic/claude-sonnet-5" }Use when you need a specific model. An explicit model bypasses the router: no failover, no cooldown protection, no candidate ranking, and no x-aura-router-* headers. Pinning a model means owning its availability.
Exception: with response_format JSON schema, AURA routes even an explicit model to verify schema support and validate the result.
Letting AURA choose
{ "model": "auto/quality" }AURA classifies the request, ranks eligible models, and calls the winner — up to four attempts on retryable failures. Prefer this for failover and cost-aware selection.
Tiers
| Tier | Use for |
|---|---|
cheap | High volume, simple work |
mid | General-purpose (default when no tier is given) |
quality | Hard reasoning, long-form generation |
fast | Latency-sensitive work |
Listing models
curl "$AURA_BASE/v1/models" -H "Authorization: Bearer $AURA_KEY"The catalogue reflects your key's policy. Cache the result — listing consumes rate-limit quota.
Request options
AURA accepts the standard OpenAI request body. It adds optional metadata and routing objects.
Metadata
{
"model": "auto/mid",
"messages": [{"role": "user", "content": "..."}],
"metadata": {
"session_id": "conversation-4821",
"user_id": "u-42",
"project_id": "billing-service"
}
}session_id groups related requests. trace_id is your correlation id; AURA still returns x-request-id as the canonical request id. tenant_id in the body is ignored — tenancy comes from the API key. Do not put secrets in metadata; some providers receive the whole object.
Routing options
{
"routing": { "prefer_latency": true }
}Use real booleans — the string "true" has no effect.
Conversation history
Long-running chats can opt in to server-side context management. It is off by default. Enable it on a single request with:
-H "x-aura-context-management: true"Confirm with x-aura-router-handover on the response.
Structured output
{
"model": "auto/quality",
"messages": [{"role": "user", "content": "Give me a person record."}],
"max_tokens": 500,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "person",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"],
"additionalProperties": false
}
}
}
}Schemas must be self-contained (no external $ref). Set max_tokens generously.
Decision models
A decision model answers typed questions about a piece of text or JSON and returns calibrated probabilities, a chosen option, or a rubric score, never generated text. It is not an LLM: no streaming, no tools, no free-form output, and it never takes part in auto/* routing. Decision models live in the decision/ namespace, the way routing tiers live in auto/. The first one is TypeSafe AI's Jev. Billing is per input token; output is free.
| model value | Result |
|---|---|
decision/jev | The pinned current Jev version (today jev-1.13.0) |
decision/jev-1.13.0 | That exact version |
decision/jev-latest | TypeSafe's rolling alias |
Responses always report the version that actually served the request, so a request for decision/jev comes back as decision/jev-1.13.0. This is the same rule as auto/mid, whose responses name the concrete model that was picked. Pin a version in production if you have tuned thresholds against it.
Question types
| Type | Asks | Returns |
|---|---|---|
noul | Is this true? | noul: probability of yes, 0 to 1 |
choice | Which of these options? | choice, confidence, probabilities per option |
score | Where on this ordered scale? | score (expected level, may be fractional), confidence, legend, probabilities |
Ask every question you need in one call. Questions are evaluated in parallel, so more questions cost little extra latency. The state may be a string, an object, or an array; JSON state with labelled keys helps the model.
TypeSafe SDK (compatible)
The official TypeSafe SDKs work against AURA with one change: point base_url at the gateway (no /v1 suffix) and use your gateway key. The SDK calls POST /v1/systemone, which AURA serves as an alias of /v1/decision.
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
client = TypeSafeClient(api_key=YOUR_GATEWAY_KEY, base_url=GATEWAY)
ticket = "Hi, my Stripe integration has been failing for 3 days. I am losing sales. Please help ASAP."
response = client.system_one(
state=ticket,
questions={
"department": Choice(
instructions="Which team should handle this",
criteria={
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions",
},
),
"frustration": Score(
instructions="How frustrated the customer appears",
criteria=["Calm, just stating facts", "Frustrated but civil", "Very angry, strong language"],
),
"is_urgent": Noul(instructions="The message conveys urgency or time-sensitivity"),
},
)
response.answers["department"].choice # "technical"
response.answers["frustration"].score # 1.0
response.answers["is_urgent"].noul # 0.99
response.model # "jev-1.13.0"The JavaScript SDK (@typesafe-ai/sdk) works the same way with new TypeSafeClient({ apiKey, baseURL }). If you omit model the SDK sends jev-latest; AURA accepts bare TypeSafe model names on this endpoint.
OpenAI SDK (compatible)
The same models are reachable through POST /v1/chat/completions with model: "decision/...", so the OpenAI SDKs work unchanged. Describe the decision as a structured output and AURA translates the schema into questions: a string field with enum becomes a choice, a boolean becomes a noul, and a bounded integer (enum or minimum/maximum) becomes a score. The reply is a normal chat.completion whose message.content is a JSON object matching your schema; the raw probabilities and confidence are in message.decision.
from typing import Literal
from openai import OpenAI
from pydantic import BaseModel, Field
class Triage(BaseModel):
department: Literal["billing", "technical", "sales"] = Field(description="Which team should handle this")
frustration: int = Field(ge=0, le=2, description="0 calm, 1 frustrated but civil, 2 very angry")
is_urgent: bool = Field(description="The message conveys urgency or time-sensitivity")
client = OpenAI(api_key=YOUR_GATEWAY_KEY, base_url=f"{GATEWAY}/v1")
r = client.beta.chat.completions.parse(
model="decision/jev",
messages=[{"role": "user", "content": ticket}],
response_format=Triage,
)
r.choices[0].message.parsed # Triage(department='technical', frustration=1, is_urgent=True)
r.choices[0].message.decision # raw answers with probabilities and confidence
r.model # "decision/jev-1.13.0"Optional schema extensions: x-criteria (option descriptions on a string enum, or {"true", "false"} on a boolean), x-levels (ordered rubric labels on an integer), x-threshold (yes/no cut-off, default 0.5). Only flat schemas are supported. stream: true, tools, images, and free-text string fields are rejected with a 400 that names the offending field in param. Power users can send response_format: {"type": "questions", "questions": {...}} to pass questions through verbatim and receive the raw answers as content.
Raw HTTP
Without an SDK, call POST /v1/decision with { model, state, questions }, where each question is { type, instructions, criteria }. The response is { object: "decision", provider, model, answers, usage } with one answer per question name. The exact schemas and a runnable example are in Swagger.
curl -s -X POST "$GATEWAY/v1/decision" \
-H "Authorization: Bearer $YOUR_GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"decision/jev","state":"I was charged twice for order A-104.","questions":{"is_billing":{"type":"noul","instructions":"Is this message about billing?"}}}'Streaming
curl -N "$AURA_BASE/v1/chat/completions" \
-H "Authorization: Bearer $AURA_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto/fast",
"messages": [{"role": "user", "content": "Count from 1 to 10."}],
"stream": true,
"stream_options": {"include_usage": true}
}'Events arrive as data: frames; the stream ends with data: [DONE]. Use -N with curl. Interrupted streams may look complete — validate shape or length when it matters.
Response headers
Read x-aura-* headers (legacy x-portkey- / x-continuum- duplicates are deprecated).
x-request-id— quote when reporting problemsx-aura-provider— provider that served the request (e.g.openai,jev)openai-model— the exact model served (e.g.openai/gpt-4o-mini,decision/jev-1.13.0)x-aura-credential-source—auraorcustomer(bring-your-own key)x-aura-router-attempts— models tried, in order (automatic routing only)x-aura-response-cost— USD cost (non-streaming)x-aura-ratelimit-*— limits and remaining quotaretry-after— on 429 and some 503 responses
Errors
{
"error": {
"message": "Model \"openai/gpt-4o\" is not accessible with this API key.",
"type": "permission_error",
"param": "model",
"code": "model_not_allowed"
}
}Branch on code. Common cases:
| Status | Code | What to do |
|---|---|---|
| 401 | invalid_api_key | Check the key |
| 403 | key_suspended | Contact your administrator |
| 403 | model_not_allowed | Use a permitted model or widen policy |
| 429 | rate_limit_exceeded | Wait for retry-after |
| 400 | invalid model parameter | Namespace the model (openai/gpt-4o) |
| 422 | json_schema_validation_failed | Simplify schema or raise tier / max_tokens |
| 404 | model_not_found | Decision endpoints only accept decision/… models |
| 400 | decision_streaming_unsupported | Decision models never stream; drop stream |
| 422 | decision_validation_error | The decision provider rejected a question; message says which |
| 429 | decision_rate_limited | Wait for retry-after |
| 503 | decision_upstream_overloaded | Back off and retry |
Honour retry-after. Back off with jitter on 503. Auto-routed 500s may already have retried internally.
Troubleshooting
| Symptom | Likely cause | What to try |
|---|---|---|
| 400 invalid model on a name that works with OpenAI | Model not namespaced | Use openai/gpt-4o, not gpt-4o |
| 403 on a model you see elsewhere | Key policy differs per key | Call GET /v1/models with the same key you use in production |
| 429 sooner than expected | Listing models uses quota | Cache /v1/models; avoid polling it in a tight loop |
| Different model than you expected | Automatic routing chose another candidate | Read x-aura-router-attempts or use routing diagnostics |
| OpenAI SDK works; AURA returns 400 | Base URL or model id | Point the SDK at AURA_BASE and namespace every model |
Routing diagnostics
Use the classify endpoints to see which model AURA would pick — and why — without spending tokens on a provider call:
curl "$AURA_BASE/v1/classify/auto?verbose=true" \
-H "Authorization: Bearer $AURA_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"messages": [{"role": "user", "content": "Write a red-black tree in Rust."}],
"metadata": {"domain": "code"}
}'With ?verbose=true you see the candidate pool, scores, and filters — without calling a provider.
OpenAPI
When interactive documentation is enabled on your deployment:
/doc— OpenAPI JSON/swagger— interactive reference
Use the generated reference for exact schemas; use this page for how and why.
Console links
- Keys — issue keys and set model policy
- Overview — usage, spend, and health
- Traces — debug a single request end to end
