Relay docs

Providers

A provider is an upstream LLM endpoint. Relay ships three adapters, all written on raw HttpClient (no vendor SDKs):

AdapterServesCapabilities
openaiOpenAI, plus any OpenAI-compatible endpoint — Azure Foundry and Ollama route through this adapter.Chat, embeddings, realtime audio.
anthropicAnthropic Messages API — including Azure Foundry's Anthropic route.Chat (an embeddings request returns 400 unsupported_capability).
geminiGoogle 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.

ModelsBase URL ends withWire formatAdapter used
OpenAI (GPT…)/models (Azure AI Model Inference) or /openai/v1 (Azure OpenAI)OpenAI chat/completionsopenai
Anthropic (Claude…)/anthropic/v1Anthropic Messagesanthropic

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, or api-key for an .azure.com host), relays SSE. Preserves query strings (so Azure's ?api-version= survives) and forwards unknown params (tools, response_format, …) — except Anthropic-only output_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 system messages to the top-level system field, defaults the required max_tokens (4096), maps stop_reason → finish_reason, and folds Anthropic's typed SSE events into OpenAI-shaped chunks. Tools, tool_choice and parallel_tool_calls are translated both ways; images and PDF file parts become image / document blocks; a cache_control on a system text part or a tool definition, and an output_config (effort) in the body, are forwarded — see Chat completions. Cache writes are reported as prompt_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 become inlineData; 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.