Providers
A provider is an upstream LLM endpoint. Relay ships three adapters, all written on raw HttpClient (no vendor SDKs):
| Adapter | Serves | Capabilities |
|---|---|---|
openai | OpenAI, plus any OpenAI-compatible endpoint — Azure Foundry and Ollama route through this adapter. | Chat, embeddings, realtime audio. |
anthropic | Anthropic Messages API — including Azure Foundry's Anthropic route. | Chat (an embeddings request returns 400 unsupported_capability). |
gemini | Google Gemini (generateContent / streamGenerateContent / batchEmbedContents). | Chat, embeddings. |
The provider's family (OpenAI, Anthropic, Gemini, Azure Foundry, Ollama) picks the adapter and the credential header, and tells the panel which discovery fields to show.
#Azure Foundry: two APIs, one resource
One Foundry resource serves several inference APIs on different paths, and a provider's base URL is what decides which one it talks to. This is the only place in Relay where the family and the wire format come apart, so it is worth setting out.
| Models | Base URL ends with | Wire format | Adapter used |
|---|---|---|---|
| OpenAI (GPT…) | /models (Azure AI Model Inference) or /openai/v1 (Azure OpenAI) | OpenAI chat/completions | openai |
| Anthropic (Claude…) | /anthropic/v1 | Anthropic Messages | anthropic |
Keep the family as Azure Foundry for both. Relay picks the adapter from the URL: an AzureFoundry provider whose path contains an /anthropic/ segment routes to the Anthropic adapter. Do not relabel the provider's family to Anthropic to force the body shape — the family is also what enables the Azure discovery fields in the panel and what makes model discovery authenticate the Azure way, so relabelling silently costs you those.
Because a provider row holds exactly one base URL, serving both families from one resource means two provider rows pointing at the same host with different paths — and the same key, since a Cognitive Services account shares one key across its openai.azure.com, services.ai.azure.com and cognitiveservices.azure.com hostnames.
A worked example:
Type Azure Foundry
Base URL https://<resource>.services.ai.azure.com/anthropic/v1
API key the Azure resource key (portal → Keys and Endpoint)
Model public alias → provider model id claude-haiku-4-5
Relay appends messages and authenticates with x-api-key — that route is Anthropic-compatible and takes Anthropic's header, not Azure's api-key.
#When it returns 401 or 404
Both of Azure's rejections here are unhelpfully generic, so match on what changes rather than on the text:
- 401 "invalid subscription key or wrong API endpoint" — Azure returns this for a wrong key and for a credential header it does not recognise, so the two cannot be told apart by reading it. Check the key first: a provider key is what Relay presents upstream, so it must be the Azure resource key. An
app_live_…value is a Relay gateway key — the credential your apps use to call Relay — and pointing it upstream is the common mix-up. - 404
api_not_supported— the route does not serve that model. The endpoint is right for some models and wrong for this one; check which of the paths above serves it.
To test a route without involving Relay, POST to it directly. A 401 means the route exists and only the credential was refused; a genuine 404 (Resource not found) means the path is not there at all. Watching a 401 turn into a 400 or 200 as you fix the key is how you confirm the header is right.
#How adapters translate
Your apps always speak the OpenAI wire format. Each adapter maps that neutral shape to and from its provider:
- OpenAI — largely passthrough; rewrites the model id, sets the credential (
Authorization: Bearer, orapi-keyfor an.azure.comhost), relays SSE. Preserves query strings (so Azure's?api-version=survives) and forwards unknown params (tools,response_format, …) — except Anthropic-onlyoutput_config, which it drops so a fallback to a non-Anthropic model is unaffected. On a stream it asks for a final usage chunk (stream_options.include_usage) so streamed calls are costed, and shortens any tool-call id over OpenAI's 40-character limit so a conversation can move between providers. - Anthropic — hoists
systemmessages to the top-levelsystemfield, defaults the requiredmax_tokens(4096), mapsstop_reason→finish_reason, and folds Anthropic's typed SSE events into OpenAI-shaped chunks. Tools,tool_choiceandparallel_tool_callsare translated both ways; images and PDFfileparts becomeimage/documentblocks; acache_controlon a system text part or a tool definition, and anoutput_config(effort) in the body, are forwarded — see Chat completions. Cache writes are reported asprompt_tokens_details.cache_creation_tokens. - Gemini — maps the request, response and streaming to Gemini's format. Tools become
functionDeclarations(JSON Schema reduced to the subset Gemini accepts),tool_choice→toolConfig, calls and results →functionCall/functionResponse; Gemini 3 thought signatures ride inside the minted tool-call id so a stateless client that echoes the id keeps working on any instance. Data-URL images becomeinlineData;https://images are refused. Cached and thought tokens are reported.
Because Anthropic and Gemini require a user message, Relay's agent pipeline promotes a self-contained system prompt to a user turn when no user message is present — so the same agent runs on every provider.
#Secrets
A provider's API key is entered in the panel and stored encrypted at rest (AES-256-GCM with Encryption:MasterKey, which must be the same value on the panel host and the gateway). The gateway decrypts it per call; it is never returned to the browser, and the list shows only whether a credential is encrypted, from config or missing. A decrypt failure is 500 provider_misconfigured, which almost always means the two hosts' master keys differ.
Alternatively leave the key blank and set a SecretRef — the name of a config key that ConfigurationProviderSecretResolver reads at call time — for teams that prefer rotating keys through configuration. A stored key wins over SecretRef. Ollama providers need neither. See Configuration & secrets.
#Managing providers
Add and edit providers in the panel under Providers: a name, the family, the base URL, the API key (or SecretRef), an optional realtime endpoint override for voice (see Realtime), and — for Azure Foundry — the optional deployment discovery fields (subscription, resource group, account, and a read-only service principal's tenant, client id and secret, the last stored encrypted), which let the model form list real deployment names rather than base models. Then create Models whose public alias maps to a provider model id; the model form can list what the provider serves and look up Azure retail prices — see Models.
Copy to workspace… duplicates a provider into another workspace you are an active member of, with its stored key carried as ciphertext unless you untick Copy the stored API key. Copying a model brings its provider along when the target has no provider of the same name and type.
Health and rolling metrics are at GET /v1/providers/health and GET /v1/models/metrics, and on the panel's Settings page.