Skip to content
ShyftLabs

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 valueResult
gpt-4o400 — invalid model parameter
openai/gpt-4oRoutes to that exact model
auto/midAURA picks a model for you
anthropic/auto/qualityAURA picks, restricted to Anthropic
decision/jevA 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.

EndpointPurpose
POST /v1/chat/completionsThe primary endpoint. Chat-style inference
POST /v1/completionsOlder text-completion style
POST /v1/embeddingsEmbedding vectors
GET /v1/modelsModels this key may call, filtered by policy
GET /v1/models/{model}One model, including structured-output support
GET /v1/keyInspect the calling key: policy, spend, remaining quota
POST /v1/compressPrompt compression
POST /v1/decisionDecision models: typed questions → calibrated answers
POST /v1/systemoneTypeSafe-compatible alias of /v1/decision
POST /v1/classify/autoShow 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

CredentialReachesTypical holder
Managed API key (sk-...)/v1 inferenceAn application
Administrator key/v1 and /adminA provisioning script
Console session/mgmtA 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

FormMeaning
provider/model_idUse exactly this model
autoAURA picks, all providers, default tier
auto/tierAURA picks, all providers, named tier
provider/autoAURA picks, one provider, default tier
provider/auto/tierAURA 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

TierUse for
cheapHigh volume, simple work
midGeneral-purpose (default when no tier is given)
qualityHard reasoning, long-form generation
fastLatency-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 valueResult
decision/jevThe pinned current Jev version (today jev-1.13.0)
decision/jev-1.13.0That exact version
decision/jev-latestTypeSafe'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

TypeAsksReturns
noulIs this true?noul: probability of yes, 0 to 1
choiceWhich of these options?choice, confidence, probabilities per option
scoreWhere 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 problems
  • x-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 — aura or customer (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 quota
  • retry-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:

StatusCodeWhat to do
401invalid_api_keyCheck the key
403key_suspendedContact your administrator
403model_not_allowedUse a permitted model or widen policy
429rate_limit_exceededWait for retry-after
400invalid model parameterNamespace the model (openai/gpt-4o)
422json_schema_validation_failedSimplify schema or raise tier / max_tokens
404model_not_foundDecision endpoints only accept decision/… models
400decision_streaming_unsupportedDecision models never stream; drop stream
422decision_validation_errorThe decision provider rejected a question; message says which
429decision_rate_limitedWait for retry-after
503decision_upstream_overloadedBack off and retry

Honour retry-after. Back off with jitter on 503. Auto-routed 500s may already have retried internally.

Troubleshooting

SymptomLikely causeWhat to try
400 invalid model on a name that works with OpenAIModel not namespacedUse openai/gpt-4o, not gpt-4o
403 on a model you see elsewhereKey policy differs per keyCall GET /v1/models with the same key you use in production
429 sooner than expectedListing models uses quotaCache /v1/models; avoid polling it in a tight loop
Different model than you expectedAutomatic routing chose another candidateRead x-aura-router-attempts or use routing diagnostics
OpenAI SDK works; AURA returns 400Base URL or model idPoint 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.