Every tool that speaks OpenAI, pointed at one endpoint
Nearly every AI coding tool and framework ships one assumption: you have an OpenAI key and you talk to OpenAI. Almost all of them also expose the one setting that breaks that assumption. Change it once and every model becomes reachable. Without waiting for tool support to add the vendor you actually want to pay for.
The three values that decide everything
Every integration on this page is the same three values wearing different clothes. Learn them once and the per-tool instructions become trivial:
| Value | What it is | What breaks if you get it wrong |
|---|---|---|
| Base URL | https://aicomp.ai/v1 | Doubling the /v1 gives 404; leaving it empty sends your request to the vendor you were trying to avoid. |
| API key | One gateway key | Vendor keys are rejected by other vendors' models That is the whole reason the endpoint exists. |
| Model ID | Exact string from GET /models | No fuzzy matching anywhere. A wrong suffix is a hard 404, not a fallback. |
curl https://aicomp.ai/v1/models \
-H "Authorization: Bearer sk-your-gateway-key"
A JSON list of model IDs means the key is good. Invalid token means it was copied wrong.
The one line every tool and framework needs
curl https://aicomp.ai/v1/chat/completions \
-H "Authorization: Bearer $GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-v4-pro","max_tokens":256,\
"messages":[{"role":"user","content":"Reply with the word ok."}]}'
Before touching any tool config, run that command. If it returns a completion, you have proven all three values work. Every tool-level failure from this point on is a configuration problem in the tool, not an API problem.
Tool matrix
| Tool | Where the setting lives | Exact name | Walkthrough |
|---|---|---|---|
| Claude Code | Environment variables | ANTHROPIC_BASE_URL | Guide |
| Cursor | Settings → Models | Override OpenAI base URL | Guide |
| OpenAI SDK (Python) | Constructor argument | base_url | Guide |
| OpenAI SDK (Node) | Constructor argument | baseURL (capitalised) | Guide |
| LangChain | Chat model constructor | configuration.baseURL | Guide |
| LiteLLM | Per-call argument or proxy YAML | api_base | Guide |
| Vercel AI SDK | Provider factory | createOpenAI({ baseURL }) | Guide |
| Cline | Settings → API Provider | Base URL field | Guide |
| Aider | .aider.conf.yml or env var | openai-api-base + openai/ prefix | Guide |
| Continue | ~/.continue/config.yaml | apiBase with provider: openai | Guide |
| Open WebUI | Container environment | OPENAI_API_BASE_URL | Guide |
| Zed | settings.json | language_models.openai_compatible | Guide |
| Windsurf | Cascade provider settings | OpenAI Compatible provider + base URL | Guide |
| Dify | Console → Model Provider | OpenAI-API-compatible entry | Guide |
| Pydantic AI | Provider object | OpenAIProvider(base_url=…) | Guide |
| n8n | OpenAI credential | Base URL (additional fields) | Guide |
| OpenHands | config.toml or LLM_ vars | base_url + openai/ model prefix | Guide |
| LibreChat | librechat.yaml | endpoints.custom[].baseURL | Guide |
| Roo Code | Settings → API Provider | OpenAI Compatible + provider/model ID | Guide |
| AnythingLLM | Environment file | GENERIC_OPEN_AI_BASE_PATH | Guide |
| Codex CLI | ~/.codex/config.toml or env | OPENAI_BASE_URL + wire_api = "responses" | Guide |
| Gemini CLI | ~/.gemini/settings.json | api.baseUrl + /auth selection | Guide |
| CrewAI | Constructor argument | LLM(base_url=…, custom_openai=True) | Guide |
| DSPy | Constructor argument | dspy.LM("openai/…", api_base=…) | Guide |
| LangChain | Constructor argument (Python / TS) | ChatOpenAI(base_url=…) — TS: configuration.baseURL | Guide |
| LlamaIndex | Settings.llm + Settings.embed_model | OpenAILike(api_base=…, is_chat_model=True) | Guide |
| AutoGen | Constructor argument | OpenAIChatCompletionClient(base_url=…, model_info=…) | Guide |
| OpenAI Agents SDK | AsyncOpenAI client | base_url=… + set_tracing_disabled() | Guide |
| OpenCode | opencode.json at the project root | settings.baseURL (v2) / options.baseURL (v1) | Guide |
| Kilo Code | Settings → Providers → Custom provider | Base URL (accepts a full endpoint URL) + flat provider ID | Guide |
| CodeCompanion.nvim | adapters + four strategies | env.url (defaults to Ollama) + per-strategy adapter | Guide |
| avante.nvim | opts.providers table | __inherited_from = "openai" + version-prefix endpoint | Guide |
| Goose | Env vars or ~/.config/goose/ | GOOSE_MODEL + OPENAI_HOST (not _BASE_URL) | Guide |
| Crush | ~/.config/crush/crush.json | type is openai in the README, openai-compat elsewhere | Guide |
| Semantic Kernel | AddOpenAIChatCompletion / async_client | .NET needs SKEXP0010 suppressed; Python has no base-URL argument | Guide |
| LobeChat | Settings panel or OPENAI_PROXY_URL | OPENAI_PROXY_URL (not _BASE_URL) + OPENAI_MODEL_LIST | Guide |
| gptel (Emacs) | gptel-make-openai in init.el | URL is protocol://host + endpoint; :host takes no scheme | Guide |
| Spring AI | spring.ai.openai.* properties | base-url has no /v1; completions-path supplies it | Guide |
| Cherry Studio | Settings → Model Services → Add provider | Address field appends /v1/chat/completions; trailing # stops it | Guide |
| Plandex | plandex models custom JSON file | apiKeyEnvVar holds a variable name, not a key; baseUrl ends in /v1 | Guide |
| Agno | OpenAILike(id=, base_url=) in Python | id and api_key default to not-provided; embedder is separate | Guide |
| Open Interpreter | --api_base / interpreter.llm.api_base | api_key is mandatory (fake_key); context limits are not inferred | Guide |
| Cody | cody.dev.models array in VS Code settings | inputTokens defaults to 7000; entry needs an editor restart | Guide |
| Mastra | createOpenAI({ baseURL }) | baseURL on the model call is ignored; model id carries a gateway prefix | Guide |
| Jan | Settings → Model Providers → Add Provider | API format is chosen before the URL; base URL must carry /v1 | Guide |
| Smolagents | LiteLLMModel or OpenAIServerModel | Which class you built decides whether the model id takes an openai/ prefix | Guide |
| Bolt.diy | OPENAI_LIKE_API_BASE_URL in .env.local | Provider ships disabled; ids come from your /models route, never typed | Guide |
| Prism PHP | providers.openai.url in config/prism.php | Default already ends in /v1; Laravel config cache hides .env edits | Guide |
| Flowise | Base Path on the ChatOpenAI Custom node | Plain ChatOpenAI has no Base Path field; Base Path is stored per node | Guide |
| Void | Settings → Providers → OpenAI Compatible | One URL, three model slots; adding a model is not the same as selecting it | Guide |
| LangChain4j | OpenAiChatModel.builder().baseUrl() | apiKey is mandatory at build time; Spring prefix is open-ai | Guide |
| Langflow | Settings → Model Providers → OpenAI Compatible | Provider exists from 1.11 only; SSRF blocks localhost until allowlisted | Guide |
| RAGFlow | Model providers → OpenAI-API-Compatible instance | Max tokens never reaches the request; /v1 auto-append is version-bound | Guide |
| Tabby | [model.completion.http] / [model.chat.http] | Completion needs a prompt_template; /v1 rule flips by connector | Guide |
| aichat | clients: list in config.yaml | Three tools share the name; forks want api_url not api_base | Guide |
| claude-code-router | Providers[] in config.json | Two schema layouts circulate; transformers gate tool calling | Guide |
| MaxKB | Model providers → OpenAI | Base model is validated at save; application API is the opposite direction | Guide |
| Trae | Settings → Models → Custom Config | URL field is base or full by a toggle; /v1 ownership flips with it | Guide |
| Zencoder | providers in settings.json | UI key screen is Anthropic/OpenAI only; the generic key has two underscores | Guide |
| Twinny | twinny.apiUrl / twinny.fimApiUrl | Chat and completion are separate routes; completion fails with silence | Guide |
| Haystack | api_base_url on the LLM components | The component appends the route; the embedder is a second endpoint | Guide |
| Raycast AI | providers.yaml | base_url excludes the route; the abilities block is a promise | Guide |
| Ellama | ellama-provider (the llm package) | Two constructors differ by one word and target different hosts | Guide |
Menu labels move between versions; the configuration key or environment variable does not. Where the two disagree in our guides, trust the variable.
The three failures behind most support tickets
| Symptom | Cause | Fix |
|---|---|---|
| 404 on every request | Doubled path — the SDK appends /chat/completions to your base URL | Strip the trailing path so the URL ends at /v1 |
| Setting appears to do nothing | Wrong casing or wrong key name (baseUrl is silently ignored by the Node SDK, which wants baseURL) | Copy the exact name from the matrix above; several tools also only re-read it on a cold start |
| 401 with a key you just created | Trailing whitespace, or the tool is still using its own pooled quota | Re-copy the key, restart the tool, then confirm the request shows up in your usage log |
What it costs to run an agent all day
Coding agents are output-heavy: short prompts in, long diffs out. That is why the output column matters more than the input column everyone compares. The table below prices one heavy day of agent work, 200k input, 60k output, across twelve models that people actually route to from these tools.
| Model | Vendor | Rate in / out per 1M | Per day | Per month |
|---|---|---|---|---|
| gpt-5.6-luna | OpenAI | $0.1 / $0.6 | $0.06 | $1 |
| deepseek-v4-flash | DeepSeek | $0.15 / $0.6 | $0.07 | $1 |
| MiniMax-M3 | MiniMax | $0.15 / $0.6 | $0.07 | $1 |
| gemini-3.7-flash | $0.375 / $1.875 | $0.19 | $4 | |
| claude-haiku-4-5-20251001 | Anthropic | $0.5 / $2.5 | $0.25 | $5 |
| deepseek-v4-pro | DeepSeek | $0.66 / $1.98 | $0.25 | $5 |
| glm-5.3 | Zhipu | $0.7 / $2.2 | $0.27 | $5 |
| qwen3.8-max | Alibaba | $1 / $3 | $0.38 | $8 |
| claude-sonnet-5 | Anthropic | $1 / $5 | $0.50 | $10 |
| gpt-5.6-terra | OpenAI | $1 / $6 | $0.56 | $11 |
| kimi-k3 | Moonshot | $1.5 / $7.5 | $0.75 | $15 |
| claude-opus-5 | Anthropic | $2.5 / $12.5 | $1.25 | $25 |
When it is configured but still not working
Every page above ends with a working setup. This is where to go when one does not — the ten diagnostics below cover the failures that show up after the configuration looks correct, which is where most of the time actually goes.
| Symptom | Go here |
|---|---|
| Requests still go to the default host with no error | Base URL not working, seven causes, in order |
| A status code you cannot interpret, or a 200 with nothing in it | Endpoint error codes: 401 vs 404 |
| 429s, or retries that quietly multiply the bill | LiteLLM retries and timeouts |
| A coding agent that rate-limits mid-session | Claude Code 429 — which limit you hit |
| Agents or frameworks whose tools silently stop working | AutoGen — declare capabilities or tools fail |
| A model id that resolves somewhere you did not intend | LiteLLM model names — the prefix is a routing instruction |
| Token counts that vanish the moment you turn streaming on | Streaming and the usage chunk you are not getting |
| An empty model picker with a key and URL that both work | Read /v1/models before you trust the dropdown |
| A context error, or responses that come back quietly short | context_length_exceeded — and the cap nobody mentions |
| A 401 that survives regenerating the key, or a 404 that appears once you do | Cline 401 vs 404 — the code names the field |
The shared idea across all ten: a URL that is never read and a URL that is wrong look identical, and authentication happens before routing So a 401 localises to the credential and a 404 to the path. The same logic runs one layer down: a name that routes and a name that resolves are different things, and only one of them tells you when it failed.
Change models without touching credentials
The reason to keep one endpoint is that switching models stops being a project. Nothing about the key or the URL changes — only the string you send:
# Same key, same URL, different partner:
# change this one line and the entire bill changes shape
export MODEL=deepseek-v4-flash
curl "$GATEWAY_BASE_URL/chat/completions" \
-H "Authorization: Bearer $GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d "{{\"model\":\"$MODEL\",\"max_tokens\":128,\
\"messages\":[{{\"role\":\"user\",\"content\":\"ok?\"}}]}}"
Put that environment variable somewhere every tool reads from and you can promote a failing call to a stronger model, or demote an expensive one to a cheaper tier, without opening a single settings panel. In practice this is how teams end up running three models at once: a strong one for anything touching production code, a mid-priced one for the daily grind, and an inexpensive one for summaries and autocomplete.
What "OpenAI-compatible" does not promise
The phrase is narrower than it sounds. It describes a request shape, not a guarantee that every feature survives the trip:
| Guaranteed by the wire format | Not guaranteed |
|---|---|
/chat/completions with messages and streaming | Identical tokenisation — the same prompt costs different amounts on different models |
| Tool / function calling messages | The same tool schemas being equally reliable across model families |
| Usage reporting and error codes | Identical rate limits, context windows or safety behaviour |
Model discovery via GET /models | Embeddings, image or audio routes being present at all |
Read that as: switching models through one endpoint is cheap, but it is not free. Budget ten minutes per new model to re-check tool reliability and cost behaviour rather than assuming the previous model's results transfer.
Rollout checklist
- Prove it with curl first. Every tool-level failure is cheaper to diagnose outside the tool.
- Put the three values in the environment. Base URL, key, model ID (never inline them in a file that gets committed).
- Confirm in your usage log, not in the UI. A green checkmark means a request succeeded, not that it billed to you.
- Pin the model ID explicitly. Anything defaulting to a snapshot will silently change behaviour when your endpoint's catalogue updates.
- Re-run your own evaluation prompt after switching. Tokenisation differs, so yesterday's cost per task is not today's.
https://aicomp.ai/v1).
Create one free →
FAQ
Which base URL do I use for all of these tools?
https://aicomp.ai/v1, the same value everywhere. What changes per tool is only where you paste it: an environment variable, a constructor argument, or a settings field.
Why do my requests 404 after I paste the base URL?
Almost always a doubled path. The OpenAI SDKs append /chat/completions to whatever you give them, so if your base URL already ends in /v1 do not add another one. Watch for the opposite failure too: an empty base URL silently goes to the vendor's own endpoint.
The tool has a model dropdown and my model is not in it.
Dropdowns are usually a static list shipped with the tool, not a call to /models. Pick the manual or 'custom model' option and type the exact ID from GET /models. Vendor APIs reject model names they do not recognise There is no fuzzy matching.
Can I switch models without reconfiguring the tool?
Yes, that is the point of one endpoint: only the model ID changes. Keep the base URL in an environment variable so reverting is instant, and keep one key instead of one per vendor.
How do I know the override actually took effect?
Send one request, then check your gateway usage log. A request appearing there is the only reliable proof. A 200 response can also come from the tool's own pooled quota if the setting was ignored.
Which tools accept an OpenAI-compatible endpoint?
Broadly four groups: coding agents (Claude Code, Cursor, Cline, Aider, Continue), SDKs and routers (OpenAI and Anthropic SDKs, LiteLLM, Vercel AI SDK, LangChain), chat front-ends (Open WebUI), and automation platforms (n8n). The mechanism differs per group (some expose a base URL field, some only read an environment variable), and a few need a provider selection before the field appears at all. The matrix above maps each one to its setting.
Do I need a separate API key for each tool?
No, one key works across all of them, which is the point of routing them through a shared endpoint. Issuing separate keys per tool is still worth doing, though: it lets you see per-tool spend in the usage log and revoke one integration without disturbing the rest. Cost attribution is the reason to split keys, never compatibility.
Related
- LiteLLM: one interface, per-model api_base, and fallbacks
- Vercel AI SDK with a custom endpoint
- Cline with an OpenAI-compatible provider
- Claude Code against your own key
- Cursor with an overridden base URL
- Cursor Agent mode: which surfaces reach your endpoint
- Cursor Agent stalls while chat answers
- What OpenAI-compatible actually guarantees
- No cheapest model — only a cheapest token mix
- Full price index
- What one endpoint makes possible: a fallback chain in one config