Vendor catalog
The vendor catalog lists the OpenAI-compatible LLM vendors AIControls knows by name. It ships inside AIControls. You can add vendors to it, or replace a vendor's entry, with an operator vendor catalog file. Each entry sets the vendor's endpoints, the hosts its key may be sent to, the error responses that mean its credit or quota has run out, and its data-handling defaults.
The vendor id is the provider name AIControls records for the vendor's traffic, in the audit log, in cost reports, and in data-handling policy (object.llm.upstream.provider).
Vendors
| Id | Vendor | Tier | Endpoints, in preference order | Hosts | Jurisdiction |
|---|---|---|---|---|---|
deepseek | DeepSeek | first-class | Anthropic https://api.deepseek.com/anthropic, OpenAI Chat https://api.deepseek.com | api.deepseek.com | CN |
moonshot | Moonshot (Kimi) | first-class | Anthropic https://api.moonshot.ai/anthropic, OpenAI Chat https://api.moonshot.ai/v1 | api.moonshot.ai, api.moonshot.cn | CN |
zai | Z.ai (GLM) | first-class | Anthropic https://api.z.ai/api/anthropic, OpenAI Chat https://api.z.ai/api/paas/v4 | api.z.ai, open.bigmodel.cn | CN |
openrouter | OpenRouter | first-class | Anthropic and OpenAI Chat https://openrouter.ai/api/v1 | openrouter.ai | — |
fireworks | Fireworks AI | first-class | Anthropic and OpenAI Chat https://api.fireworks.ai/inference | api.fireworks.ai | US |
groq | Groq | first-class | OpenAI Chat https://api.groq.com/openai/v1 | api.groq.com | US |
cerebras | Cerebras | first-class | OpenAI Chat https://api.cerebras.ai/v1 | api.cerebras.ai | US |
together | Together AI | first-class | OpenAI Chat https://api.together.xyz/v1 | api.together.xyz | US |
mistral | Mistral AI | first-class | OpenAI Chat https://api.mistral.ai/v1 | api.mistral.ai | EU |
xai | xAI | first-class | OpenAI Chat https://api.x.ai/v1 | api.x.ai | US |
perplexity | Perplexity | first-class | OpenAI Chat https://api.perplexity.ai | api.perplexity.ai | US |
gemini | Google Gemini | first-class | OpenAI Chat https://generativelanguage.googleapis.com/v1beta/openai | generativelanguage.googleapis.com | — |
sarvam | Sarvam AI | community | OpenAI Chat https://api.sarvam.ai/v1 | api.sarvam.ai | — |
huggingface | Hugging Face Inference Providers | community | OpenAI Chat https://router.huggingface.co/v1 | router.huggingface.co | — |
nvidia-nim | NVIDIA NIM | community | OpenAI Chat https://integrate.api.nvidia.com/v1 | integrate.api.nvidia.com | — |
deepinfra | DeepInfra | community | OpenAI Chat https://api.deepinfra.com/v1/openai | api.deepinfra.com | — |
nebius | Nebius Token Factory | community | OpenAI Chat https://api.tokenfactory.nebius.com/v1 | api.tokenfactory.nebius.com | — |
sambanova | SambaNova Cloud | community | OpenAI Chat https://api.sambanova.ai/v1 | api.sambanova.ai | — |
cohere | Cohere | community | OpenAI Chat https://api.cohere.ai/compatibility/v1 | api.cohere.ai, api.cohere.com | — |
alibaba | Alibaba Cloud Model Studio (Qwen) | community | OpenAI Chat https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | dashscope-intl.aliyuncs.com | — |
minimax | MiniMax | community | Anthropic https://api.minimax.io/anthropic, OpenAI Chat https://api.minimax.io/v1 | api.minimax.io | — |
baseten | Baseten Model APIs | community | OpenAI Chat https://inference.baseten.co/v1 | inference.baseten.co | — |
novita | Novita AI | community | OpenAI Chat https://api.novita.ai/openai | api.novita.ai | — |
ollama | Ollama | local | Anthropic and OpenAI Chat http://localhost:11434 | any | — |
vllm | vLLM | local | Anthropic and OpenAI Chat http://localhost:8000 | any | — |
custom | Custom OpenAI-compatible | custom | OpenAI Chat at the URL you enter | any | — |
No vendor makes a zero-data-retention or no-training guarantee by default. A policy that requires zero data retention, no prompt training, or a jurisdiction skips a vendor that doesn't meet it. To record a guarantee from your own agreement with a vendor, or for a local Ollama or vLLM server you run yourself, set the upstream's capabilities.
Tiers
- first-class: a hosted vendor with tested endpoints, credit and quota signals, request fixes, and pricing. Its base URLs are pinned to the hosts in the table.
- community: a hosted vendor whose catalog entry is complete but whose endpoints aren't tested on a schedule. It needs no
litellmProvider,quotarules, or recorded error fixtures, so the generic failover rules classify its errors and its models are priced from the cost table. It can't befeatured. Its base URLs are pinned to its hosts exactly like a first-class vendor's. The provider catalog API reports each provider'stier. - local: a model server you run, such as Ollama or vLLM, at any URL. A local upstream with no API key sends no credential at all. It never receives the credential a developer's tool sent to AIControls.
- custom: any other OpenAI-compatible endpoint. It needs a URL and usually a key, uses OpenAI Chat Completions and has no catalog pricing.
Configure a vendor upstream
Set vendor on the upstream. AIControls fills in the provider and the URL from the catalog:
llmProxy:
upstreams:
- name: deepseek-credits
vendor: deepseek
apiKeyEnv: DEEPSEEK_API_KEY
| Field | Default from the catalog |
|---|---|
provider | The vendor id, or custom:<host> for vendor: custom. Leave it empty; a different value is rejected. |
url | The vendor's first endpoint. For a vendor with an Anthropic endpoint, that is the Anthropic one, so Claude Code reaches it without translation. |
The admin API takes the same field: "vendor": "deepseek" on PUT /api/v1/llm-upstreams/{name}. In the Helm chart, set llmProxy.upstream.vendor for the single upstream, or vendor on an llmProxy.upstreams entry.
An upstream that sets provider to a vendor id, or leaves provider empty and uses a URL on a vendor's host, is treated as that vendor too.
Host pinning
When you set vendor to a first-class or community vendor, the URL must be an https URL on exactly one of its listed hosts. Any other URL is rejected, at startup for a config-file upstream and with a 400 from the admin API, so a vendor key can't be sent to a host you didn't choose. To use another URL, set vendor: custom. For example, Alibaba Cloud Model Studio is pinned to its dashscope-intl.aliyuncs.com domain, so an upstream on a per-workspace domain such as {WorkspaceId}.ap-southeast-1.maas.aliyuncs.com uses vendor: custom, as does a Baseten dedicated deployment. An upstream that names the vendor only through provider keeps the URL you configure, such as a proxy in front of the vendor.
Endpoints
A vendor with two endpoints, such as DeepSeek, needs only one URL. AIControls derives the other from it: https://api.deepseek.com/anthropic serves Anthropic clients, and https://api.deepseek.com serves OpenAI clients. The same holds for a URL on your own host, such as a proxy in front of the vendor, as long as it keeps the vendor's path layout.
Model names
Clients can name a model <vendor>/<model>, such as groq/llama-3.3-70b-versatile. The vendor receives llama-3.3-70b-versatile. An OpenAI-only vendor's upstream is created matching <vendor>/* and its catalog models. A Gemini upstream also matches gemini-*, and when it ties with a Google Vertex upstream for a bare gemini-* model, the Gemini upstream is chosen; see Model Routes.
Model list
GET /v1/models lists a vendor upstream's models from the vendor's own model list. A vendor without one, such as Z.ai or Perplexity, lists its catalog models instead. Gemini lists its models as models/<id>; AIControls lists them as <id>, such as gemini-3.8-flash, the name requests use. Set Static Model List (staticModels) or turn on Live Model Discovery (modelDiscovery) on the upstream to replace them.
URL paths
AIControls appends its request paths, such as /v1/chat/completions, to the upstream URL. Some vendors document their base URL with the /v1 already in it, such as https://api.groq.com/openai/v1. For those, AIControls drops the trailing /v1 from the URL, so either form works. For a vendor whose API has no /v1 segment, such as Z.ai's https://api.z.ai/api/paas/v4, Perplexity's https://api.perplexity.ai, or Gemini's https://generativelanguage.googleapis.com/v1beta/openai, AIControls leaves /v1 out of the request path. The same holds for a vendor whose base URL carries its version earlier in the path and doesn't end in /v1, such as DeepInfra's https://api.deepinfra.com/v1/openai: AIControls appends /chat/completions to it directly, with no second /v1. A vendor URL entered with an extra /v1, such as https://api.fireworks.ai/inference/v1 or https://api.deepseek.com/v1, works the same as the catalog form. A local Ollama or vLLM URL works with or without its /v1, such as http://gpu:8000 or http://gpu:8000/v1.
Add or replace vendors
An operator vendor catalog file adds vendors to the catalog, or replaces a catalog vendor's entry. AIControls reads it at startup and merges it over the built-in catalog. Every part of AIControls that uses the catalog uses the merged result: vendor upstreams, provider inference from a URL, <vendor>/<model> names, credit and quota signals, request quirks, data-handling defaults, and the vendor list in Settings → Providers.
Set the file's path in the config file:
llmProxy:
vendorCatalogFile: /etc/aicontrols/vendors.yaml
In the Helm chart, list the entries in llmProxy.vendorCatalog. The chart writes them to vendors.yaml in the config ConfigMap, mounts it at /etc/proxy/vendors.yaml, and sets llmProxy.vendorCatalogFile. The list is empty by default.
The file is a single YAML document holding a list of vendor entries:
# Adds Acme AI, a hosted OpenAI-compatible vendor.
- id: acme
name: Acme AI
tier: first-class
endpoints:
- format: openai-chat # or anthropic, listed first
baseURL: https://api.acme.example/v1
authHeader: bearer # bearer, x-api-key, or none
pathStyle: v1-included # default, v1-included, or no-v1
hosts: [api.acme.example]
litellmProvider: openai
modelPrefix: acme/ # clients can send acme/<model>
defaultModels: [acme-large]
modelDiscovery: true
capabilities:
zeroDataRetention: false
noPromptTraining: true
jurisdiction: EU
signup:
url: https://acme.example/keys
quota:
- status: 402 # out of credit
- status: 429
reason: rate_limit
quirks:
dropParams: [store]
# Replaces the catalog's DeepSeek entry, keeping only its OpenAI endpoint.
- id: deepseek
override: true
name: DeepSeek
tier: first-class
endpoints:
- format: openai-chat
baseURL: https://api.deepseek.com
authHeader: bearer
hosts: [api.deepseek.com]
litellmProvider: deepseek
modelPrefix: deepseek/
modelDiscovery: true
capabilities:
jurisdiction: CN
signup:
url: https://platform.deepseek.com/api_keys
quota:
- status: 402
| Field | Meaning |
|---|---|
id | The vendor id: lowercase letters, digits, and dashes. It is also the provider name. |
override | true to replace the catalog vendor with this id. The entry replaces the whole catalog entry, so give every field you need. |
name | The display name. |
tier | first-class, community, local, or custom. See Tiers. |
endpoints | One per wire format: format (anthropic or openai-chat; an anthropic endpoint is listed first), baseURL, authHeader (bearer, x-api-key, or none), and an optional pathStyle (default, v1-included, or no-v1; see URL paths). |
hosts | The hosts a first-class or community vendor's key may be sent to. Each baseURL must be an https URL on one of them, and no two vendors share a host. |
auth | key (the default), none, or optional. A local vendor with none or optional and no key sends no credential. |
litellmProvider | The vendor's provider name in the LiteLLM pricing feed. Required for a first-class vendor. |
modelPrefix | <id>/, so clients can send <id>/<model>. Set stripPrefix: false to send the prefix to the vendor too. |
defaultModels | The models listed for the vendor when it has no model list of its own. |
modelDiscovery | Whether the vendor serves a model list. |
capabilities | Data-handling defaults: zeroDataRetention, noPromptTraining, and jurisdiction. |
signup | url, where to create a key (required for a first-class or community vendor), and freeCredits. |
quota | The error responses that mean credit or quota ran out. Each rule has a status and optionally bodyType, bodyCode, messageContains, bodyContains, reason (quota_exhausted, the default, rate_limit, or auth for a rejected key reported with a status other than 401 or 403), resetFrom: message, and resetUTCOffset. |
quirks | Request fixes for the OpenAI Chat endpoint: reasoningEcho (none, strip, required, or required_with_tools), dropParams, renameParams, and rejectUnknownFields. |
featured | true to show the vendor as a featured tile in the provider pickers. A featured vendor needs an order, and a community vendor can't be featured. |
order | A positive sort key. Featured providers come first, then providers by order, then the rest by name. The built-in featured providers take 1 to 4 and 6, and OpenRouter takes 5. |
logo | The logo key the console shows. A generic logo is shown when it's omitted. |
displayName | The label in the Settings → Providers picker. Defaults to name. |
shortName | The label in the setup wizard. Defaults to the display name. |
keyPlaceholder | The placeholder in the API key field. |
testModel | The model a test call uses. Defaults to the first of defaultModels. |
family | The model family the provider catalog reports: anthropic, openai, google, or "". When it's omitted, the family comes from the endpoints: a vendor with only an OpenAI Chat endpoint is openai, and a vendor with both endpoints is "". Routing always uses the endpoints. |
The presentation fields, from featured to family, are optional. An override: true entry that omits them uses the defaults, not the catalog vendor's values.
AIControls checks the merged catalog the same way as the built-in one, and doesn't start if the file can't be read or has an error. The error names the file and the entry. An entry is rejected when:
- it uses a catalog vendor's id without
override: true. - its id is
custom, or a built-in provider such asanthropic,openai, oraws-bedrock. - it has a field AIControls doesn't know, a missing required field, or a value out of range.
- it repeats an id, or lists a host another vendor already has, or a host of a built-in provider, such as
api.openai.com. - a
baseURLhas credentials, a query string, or a fragment. Put the vendor's key on the upstream instead.
The vendor price table has no rows for a vendor you add. To price its models, add cost-table rows keyed <id>/<model>; until then, its models are priced at the fallback rate. A vendor you replace keeps the catalog's prices.
Provider catalog API
GET /api/v1/provider-catalog returns every provider the setup wizard and Settings → Providers can offer: the built-in providers, such as Anthropic, OpenAI, and AWS Bedrock, and every vendor in the catalog, including the vendors in an operator vendor catalog file. Administrators and viewers can call it. The response reflects the catalog in effect when you call it.
{
"providers": [
{
"kind": "builtin",
"id": "anthropic",
"aliases": [],
"name": "Anthropic",
"shortName": "Anthropic",
"family": "anthropic",
"formats": ["anthropic"],
"defaultURL": "https://api.anthropic.com",
"authKind": "api-key",
"keyPlaceholder": "sk-ant-...",
"keyLink": {"label": "console.anthropic.com → API Keys", "url": "https://console.anthropic.com/settings/keys"},
"testModel": "claude-haiku-4-5",
"modelDiscovery": true,
"capabilities": {"zeroDataRetention": false, "noPromptTraining": true, "jurisdiction": "US"},
"featured": true,
"order": 1,
"surfaces": ["settings", "wizard"],
"tier": "first-class"
},
{
"kind": "vendor",
"id": "openrouter",
"name": "OpenRouter (free and paid models)",
"shortName": "OpenRouter",
"featured": true,
"order": 5,
"endpoints": [{"format": "anthropic", "baseURL": "https://openrouter.ai/api/v1", "authHeader": "bearer", "pathStyle": "v1-included"}],
"models": [{"id": "qwen/qwen3-coder:free", "priced": false}],
"quotaSignals": ["HTTP 402", "HTTP 403 with message \"key limit exceeded\""]
}
],
"accessModes": [
{"id": "claude-ai", "title": "Claude.ai subscription (Pro, Max, Team)", "sub": "...", "passthrough": {"product": "anthropic", "provider": "anthropic", "label": "Claude.ai"}}
],
"custom": {"kind": "custom", "id": "custom", "name": "Custom OpenAI-compatible", "authKind": "api-key", "formats": ["openai-chat"]}
}
The second entry is shortened.
| Field | Meaning |
|---|---|
providers | The providers, featured first, then by order, then by name. Providers no picker offers are left out. |
kind | builtin for a provider AIControls handles natively, vendor for a vendor catalog entry, or custom for the Custom OpenAI-compatible entry. |
id, aliases | The provider id, and other ids stored for the same provider, such as bedrock for aws-bedrock. |
name, shortName | The Settings → Providers label, and the setup wizard label where it differs. |
family, formats | The model family, and the wire formats the provider serves: anthropic, openai-chat, openai-responses, or gemini. |
defaultURL | The URL the form fills in. It's empty when you enter a resource-specific URL, such as for Azure OpenAI. |
authKind | How an upstream authenticates: api-key, none, optional, aws, gcp, azure, or oauth-passthrough. A vendor's auth of key is api-key. |
keyPlaceholder, keyLink | The API key field placeholder, and where to get a key. A vendor's keyLink is its signup.url. |
testModel | The model a test call uses. |
featured, order, surfaces | Whether the provider is a featured tile, its sort key, and the pickers that offer it: settings, wizard. |
tier | first-class, community, local, or custom. Every built-in provider is first-class. |
| Vendor fields | A vendor also has the fields of its catalog entry: endpoints, hosts, auth, defaultModels, signup, quota, quirks, its priced models, quotaSignals, anthropicCompatible, and defaultMatchModels (the model patterns a new upstream of the vendor starts with; empty matches every model). |
accessModes | The setup wizard's choices for how developers' AI tools reach a model. A passthrough mode uses each developer's own credential for its provider. |
custom | The Custom OpenAI-compatible entry, for any other OpenAI-compatible endpoint. It's never in providers. |
Credit and quota signals
When a route member's credit or quota runs out, AIControls fails over to the next member, which uses its own key. Each vendor reports it differently, and AIControls checks the vendor's own signals before the generic ones:
| Vendor | Out of credit or quota | Treated as a rate limit |
|---|---|---|
| DeepSeek | HTTP 402 | 429 |
| Moonshot (Kimi) | 429 with error.type exceeded_current_quota_error; 402 | other 429 |
| Z.ai (GLM) | 429 with business code 1113, 1308–1310, or 1316–1321. For 1308, 1310 and 1316–1321, the reset time in the message (China Standard Time) sets how long the member is parked. | other 429 |
| OpenRouter | 402; 403 "Key limit exceeded" | 402 with in_flight_budget |
| Fireworks AI | 412; 402 | 429 |
| Cerebras, Together AI | 402 | 429 |
| xAI | 429 whose message says "used all available credits" or "monthly spending limit"; 402 | other 429 |
| Perplexity | 401 with insufficient_quota; 402 | 429 |
| Google Gemini | 429 whose quota id is a per-day quota (PerDay, reset at midnight Pacific time); 402 | 429 whose quota id is a per-minute quota (PerMinute); other 429 |
| Groq, Mistral AI | none documented | 429 |
Every vendor also gets the generic signals: HTTP 402, and the OpenAI and Anthropic insufficient_quota, billing, and credit-balance errors.
A rejected key fails over like any other key failure (HTTP 401 or 403). Gemini reports an invalid key as HTTP 400 with reason API_KEY_INVALID, which AIControls treats the same way.
The failover reason (quota_exhausted, rate_limit, or auth) shows in the proxy's failover log line and as the failure category on the failed attempt's audit row. A quota or key failure on a developer's own forwarded credential is final unless a later route member has a credential of its own.
A member whose own key runs out of credit or quota is parked: the route skips it until the response's Retry-After time. Without one, it uses the vendor's reported reset time, and without that, 15 minutes.
Request quirks
Before a request goes to a vendor's OpenAI Chat endpoint, AIControls applies the vendor's fixes. Requests to a vendor's Anthropic endpoint are sent as the client sent them.
| Vendor | Earlier reasoning in the history | Fields dropped | Fields renamed | Retries without a rejected field |
|---|---|---|---|---|
| DeepSeek | needed with tools | top_k | — | no |
| Moonshot (Kimi), Z.ai (GLM) | needed | — | — | no |
| Groq | removed | logprobs, logit_bias, top_logprobs, store | — | yes |
| Cerebras | removed | logit_bias, store | max_completion_tokens → max_tokens | yes |
| Together AI | removed | store | — | no |
| Mistral AI | removed | store, logit_bias, top_logprobs, logprobs | max_completion_tokens → max_tokens | no |
| xAI | removed | store | — | no |
| Perplexity | removed | store, logit_bias, tools, tool_choice | — | no |
| Google Gemini | removed | store | — | yes |
| Ollama, vLLM | removed | — | — | no |
For a vendor marked Retries without a rejected field, a 400 that names a top-level or per-message request field the vendor doesn't accept, such as Groq's property 'x' is unsupported or Gemini's Unknown name "x": Cannot find field, is retried once without that field. A rejected field inside a tool definition isn't retried; the 400 is returned. The proxy log records the field, so it can be added to the vendor's dropped fields.
Earlier reasoning that a vendor needs sent back (DeepSeek with tools, Moonshot, Z.ai) is kept only on the vendor's Anthropic endpoint, which Claude Code uses: thinking blocks are sent as the client sent them. Requests through a vendor's OpenAI endpoint never carry earlier reasoning (reasoning_content or reasoning), so for these vendors, send multi-turn tool use through the Anthropic endpoint.
Pricing
AIControls prices a vendor call by the vendor and the model it served, from a price table generated from the LiteLLM pricing feed for every vendor in the catalog. The table refreshes weekly with the default model pricing. Local vendors are free. See Models served by an OpenAI-compatible vendor.
The LiteLLM feed has no OpenRouter rows, so add a cost-table row keyed openrouter/<model> for an OpenRouter model you use; until then, a paid OpenRouter model is priced at the fallback rate and a :free model at $0.
Policies
In policies, object.llm.upstream.provider is the vendor id (deepseek, groq, custom:llm.example.com), so a policy that tests provider == "openai" matches only OpenAI itself, not a vendor reached through an OpenAI-compatible endpoint. object.llm.upstream.vendorTier is first-class, community, local, or custom, and object.llm.upstream.family is the wire format the upstream speaks (anthropic, openai, or empty for a dual-endpoint vendor). For example, to keep requests off every local model server:
object.llm.upstream.vendorTier == "local"