Unified Model API
Use one OpenAI-compatible URL for every provider. Any app built on the OpenAI SDK can reach Anthropic, Gemini, Bedrock, or your own vLLM server. To change the model, change the model name. The app code stays the same. Every request still gets Guardrails, costs, and logs.
Connect an App
Give your app a virtual key and the Unified Model API URL. Then name each model with its provider in front of it.
-
Add the provider keys you need on Model Providers.
-
Create a standard virtual key, and map those provider keys to it.
-
Set your app's base URL to
https://<archestra-host>/v1/unified-model, and use the virtual key as its API key. -
List the models the key can reach:
bashcurl "https://<archestra-host>/v1/unified-model/models" \ -H "Authorization: Bearer $ARCHESTRA_VIRTUAL_KEY" -
Send a request with one of those IDs:
bashcurl "https://<archestra-host>/v1/unified-model/chat/completions" \ -H "Authorization: Bearer $ARCHESTRA_VIRTUAL_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"openai:gpt-5.4","messages":[{"role":"user","content":"Hello"}]}'
The answer comes back in OpenAI format, from any provider.
- Model IDs start with the provider:
openai:gpt-5.4,anthropic:claude-sonnet-4-6. The API sends the rest of the ID to that provider. - One provider only? When the key maps to one provider, plain IDs such as
gpt-5.4also work.
Endpoints and Credentials
The Unified Model API speaks the OpenAI API. It supports these endpoints:
| Endpoint | Works with |
|---|---|
/chat/completions | All supported providers |
/responses | All supported providers except Kimi (Moonshot AI) and Kimi Code, whose thinking models need their reasoning sent back on tool-call turns, which the Responses translation does not carry |
/models | Lists only the models your credential can reach |
/embeddings | All supported providers except Anthropic, Bedrock, Cohere, and GitHub Copilot |
/images/generations | OpenAI, Azure, and Gemini image models |
/images/edits | OpenAI, Azure, and Gemini image models |
/audio/speech | OpenAI and Azure speech models |
/audio/transcriptions and /audio/translations | OpenAI and Azure models that support the requested operation |
/realtime (WebSocket) | OpenAI realtime models |
Sign in with a standard virtual key or an OAuth client token. The Unified Model API does not accept a passthrough key, a provider's own key, or an identity provider JWT.
Images and Audio
Use the same OpenAI SDK client, base URL, and virtual key for text, images, and audio. The mapped provider key must have access to the requested model.
from openai import OpenAI
client = OpenAI(base_url="https://<archestra-host>/v1/unified-model", api_key="<virtual-key>")
image = client.images.generate(model="azure:<image-deployment>", prompt="Draw a blue circle.")
with open("speech.wav", "rb") as audio:
transcript = client.audio.transcriptions.create(model="openai:<transcription-model>", file=audio)
Image responses carry the provider's image data, and transcriptions carry a text field. Speech responses are the audio bytes, and streamed transcriptions are SSE events, as the provider sends them. Add /<proxy-id> after /v1/unified-model to select a specific LLM proxy.
Audio chat models such as openai:gpt-audio answer /chat/completions in text, or in speech when the request sets modalities and audio. Their audio tokens are priced at OpenAI's audio rates.
- Provider parameters: OpenAI and Azure accept their native image parameters. Gemini returns one base64 image at its own size and rejects
quality,size,output_, and a mask on edits. Image streaming is rejected for every provider.format - Uploads and logs: audio uploads are limited to 25 MiB per file. Logs keep the text spoken and the transcript. With Keep media in logs on, they also keep each audio clip and each image an edit uploads, up to 2 MB, so the conversation view can play or show it. A larger file is marked as not kept.
- Pricing: image, speech, transcription, and translation calls are priced at the vendor's published media rates: per image token, per speech character, per minute of audio. A translation or a text-format transcript reports no usage, so Archestra reads its length from the uploaded file. A WebM upload has no readable length and records no cost. Models lists each media model's rates.
- Budgets: a priced media call counts toward cost budgets like a chat call. A cost budget rejects a call that cannot be priced, such as a translation of a WebM file, before it reaches the provider.
Azure file operations stay on the Azure provider route: a provider-qualified model name cannot select a file account, and a file id cannot replace a multipart image upload.
Realtime
Open a live voice session with the SDK's realtime client and the same base URL and virtual key. Archestra checks the key and its budgets, then forwards the session to OpenAI with the mapped provider key.
with client.realtime.connect(model="openai:gpt-realtime") as session:
session.session.update(session={"output_modalities": ["audio"]})
session.conversation.item.create(item={"type": "message", "role": "user", "content": [{"type": "input_text", "text": "Say hello."}]})
session.response.create()
for event in session:
if event.type == "response.done":
break
Each response of the session is one row under Logs → LLM Proxy, with its transcripts and its tokens and cost at OpenAI's realtime rates. With Keep media in logs on, the row also plays the spoken reply. SIP calls (call_) are not served.
- Budgets: Archestra checks the budgets again before each response, including one that starts when the user stops speaking. Once a response spends them, Archestra sends an
errorevent with codebudget_and closes the session. A budget created while a session is open is checked after each response, not before it.exceeded - Refused sessions: Archestra refuses a realtime session while Guardrails are enforced or a throughput limit applies to the caller. Neither can review a live session yet.
Fix a Failed Request
Most failures come from the model name or from which provider keys your credential can use. The response names the problem in the error message. Find it below:
| Error message | Cause and fix |
|---|---|
Model "gpt-5.4" is not available. Use a provider-qualified model id… | The ID has no provider, or your credential cannot use that model. Copy the exact ID from /models, such as openai:gpt-5.4. |
Model "anthropic:…" is scoped to provider "anthropic", but the Unified Model API virtual key is not mapped to that provider. | Edit the virtual key, and map a key for that provider to it. |
Virtual API key has no provider API keys configured. | Map at least one provider key to the virtual key. |
Model … is only served over the Responses API. | Send the request to /responses, not /chat/completions. |
Provider "…" is not yet available through the OpenAI-compatible Unified Model API. | The Unified Model API does not support that provider. Use the provider's own proxy endpoint. |
Unified Model API requests require a mapped virtual API key or LLM OAuth client access token. | You sent a passthrough key, a provider key, or an identity provider JWT. Use a standard virtual key or an OAuth client token. |
… is per-user: it can only be used through the same user's own personal credential. | A subscription key, such as GitHub Copilot, works only for the person who added it. Use your personal virtual key. |
Two problems return no error:
- A Gemini answer is empty or cut off. For Gemini,
max_also counts thinking tokens. Increase it.completion_ tokens - The model ignores an image or a file. The provider does not accept that content type. Send it inline as base64, to a provider that accepts it.
What to Know
- Reach a new provider: add its key on Model Providers, and map that key to your virtual key.
- One request shape, the provider's rules: reasoning settings, content types, and token limits still come from the provider.