If your app already uses the official OpenAI SDK, moving it to another OpenAI-compatible endpoint usually means changing two settings: the base URL and the API key. Usually you also change the model name. You don’t need a new SDK or a rewrite. This tutorial shows the change in Python, Node and curl, plus LangChain and the Vercel AI SDK. It also covers the things that tend to break, and how to roll the change out safely.
The examples use Relaykit’s endpoint, {SITE.apiBase}/v1, and {terra.id} as the model. The same steps work for any compatible provider.
What a base URL is
The SDK builds every request URL by adding a path to the base URL. The default base is https://api.openai.com/v1, so a chat request goes to https://api.openai.com/v1/chat/completions. Change the base to {SITE.apiBase}/v1 and the same call goes to {SITE.apiBase}/v1/chat/completions. The request body, the response shape and your code don’t change.
The /v1 is part of the base URL for OpenAI-format clients. Most “404 Not Found” errors after a switch come from leaving it out, or from adding it twice.
Step 1: find everything you need to change
Before editing, list every place the old provider is set up. Hardcoded URLs and model names tend to hide in scripts, tests and config files:
# Every place the old provider is configured or a model is named
git grep -n -E "api\.openai\.com|OPENAI_BASE_URL|base_url|baseURL|model\s*[=:]" -- . ':!*.lock'Step 2: the zero-code switch with environment variables
Both official SDKs read two environment variables when you create a client with no arguments: OPENAI_API_KEY and OPENAI_BASE_URL. If your code uses OpenAI() or new OpenAI() with no options, setting the variables is the whole switch:
# macOS / Linux (bash, zsh)
export OPENAI_BASE_URL=https://api.oneaiapikey.com/v1
export OPENAI_API_KEY=sk-your-relaykit-key
# Windows PowerShell
$env:OPENAI_BASE_URL = "https://api.oneaiapikey.com/v1"
$env:OPENAI_API_KEY = "sk-your-relaykit-key"from openai import OpenAI
# With no arguments the SDK reads OPENAI_API_KEY and OPENAI_BASE_URL.
client = OpenAI()One caution: don’t put these in your shell profile. Many tools on a developer machine use the OpenAI SDK and read the same variables. If you set them globally, all of those tools switch providers. Put them in the project’s .env file or the deployment’s environment instead.
Step 3: verify with curl before touching the app
Two requests tell you whether the key, the URL and the model ID are right. Test these before debugging your own code:
# 1. Does the key work, and which model IDs can it call?
curl -s $OPENAI_BASE_URL/models -H "Authorization: Bearer $OPENAI_API_KEY"
# 2. One real completion
curl -s $OPENAI_BASE_URL/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-5.6-terra", "max_tokens": 50, "messages": [{"role": "user", "content": "Say ok"}]}'The first call lists the model IDs your key can use. The second should return a short completion with a usage block. If both work, any later failure is in your app’s configuration, not in the account.
Step 4: make it explicit in code
For anything beyond a quick test, pass the settings in code, read from your own variable names. It makes the dependency visible, avoids the global-variable problem above, and lets you change the model without a deploy.
import os
from openai import OpenAI
# Your own variable names: nothing else on the machine reads them by accident.
client = OpenAI(
base_url=os.environ.get("LLM_BASE_URL", "https://api.oneaiapikey.com/v1"),
api_key=os.environ["LLM_API_KEY"],
timeout=120, # seconds; reasoning models can think for a while
max_retries=2,
)
MODEL = os.environ.get("LLM_MODEL", "gpt-5.6-terra")
resp = client.chat.completions.create(
model=MODEL,
max_tokens=300,
messages=[{"role": "user", "content": "Name three uses for a base URL setting."}],
)
print(resp.choices[0].message.content)
print(resp.usage)import OpenAI from "openai";
// new OpenAI() with no options also reads OPENAI_API_KEY and OPENAI_BASE_URL.
const client = new OpenAI({
baseURL: process.env.LLM_BASE_URL ?? "https://api.oneaiapikey.com/v1",
apiKey: process.env.LLM_API_KEY,
timeout: 120_000, // milliseconds
maxRetries: 2,
});
const MODEL = process.env.LLM_MODEL ?? "gpt-5.6-terra";
const resp = await client.chat.completions.create({
model: MODEL,
max_tokens: 300,
messages: [{ role: "user", content: "Name three uses for a base URL setting." }],
});
console.log(resp.choices[0].message.content, resp.usage);AsyncOpenAI in Python takes the same base_url and api_key arguments.
Frameworks built on the SDK
Most frameworks pass the base URL through to the SDK underneath. Two common ones:
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-5.6-terra",
base_url="https://api.oneaiapikey.com/v1",
api_key=os.environ["LLM_API_KEY"],
max_tokens=300,
)
print(llm.invoke("Say ok").content)import { createOpenAI } from "@ai-sdk/openai";
import { generateText } from "ai";
const gateway = createOpenAI({
baseURL: "https://api.oneaiapikey.com/v1",
apiKey: process.env.LLM_API_KEY,
});
// .chat() uses /chat/completions, the most widely supported endpoint.
const { text } = await generateText({
model: gateway.chat("gpt-5.6-terra"),
prompt: "Say ok",
});Step 5: change the model IDs
Model names are not portable between providers. An ID that exists at OpenAI may not exist on another endpoint, and the request will fail with a model error. These are the IDs Relaykit serves through the OpenAI format today, with current rates per 1M tokens:
| Model ID | Name | Input | Output |
|---|---|---|---|
claude-opus-5-5 | Claude Opus 5.5 | $4.40 | $22 |
claude-sonnet-5 | Claude Sonnet 5 | $2.20 | $11 |
claude-haiku-4-5 | Claude Haiku 4.5 | $1.10 | $5.50 |
gpt-6.1-sol | GPT-6.1 Sol | $2.20 | $11 |
gpt-5.6-sol | GPT-5.6 Sol | $5.50 | $33 |
gpt-5.6-terra | GPT-5.6 Terra | $2.20 | $13.20 |
grok-4.6 | Grok 4.6 | $2.20 | $6.60 |
claude-sonnet-5-5 | Claude Sonnet 5.5 | $2.20 | $11 |
grok-4.7 | Grok 4.7 | $2.20 | $6.60 |
deepseek-v4-flash | DeepSeek V4 Flash | $0.24 | $0.96 |
deepseek-v4-pro | DeepSeek V4 Pro | $1.32 | $3.96 |
Read the model from configuration, as in the examples above, rather than hardcoding it in each call. The next time you switch, it’s a config change.
What tends to break, and the fix
| Symptom | Likely cause | Fix |
|---|---|---|
404 Not Found |
Base URL without /v1, or with /v1/v1 |
The base URL ends in /v1 with no trailing path |
401 Unauthorized |
The old OpenAI key is still loaded, often from a shell profile or a CI secret | Print the key’s first 6 characters at startup and compare |
| Model not found | An old model ID | Use an ID from GET /v1/models |
400 on a parameter |
A parameter the target model doesn’t support | Remove it. Reasoning models often want max_completion_tokens instead of max_tokens |
| Timeouts on long answers | Client timeout shorter than generation time | Raise the timeout, or stream the response |
| Connection refused in production | Outbound firewall or proxy allows only the old host | Add the new API host to the allowlist |
Some endpoints are specific to one provider. Files, fine-tuning, batch jobs, Assistants, and audio or image generation are not part of what Relaykit serves. The API reference lists the endpoints we do support: chat completions, responses, messages, models and embeddings. If your app uses OpenAI-only endpoints, keep a separate client for them on OpenAI and move only the chat traffic.
OpenAI account settings such as organization and project IDs (OPENAI_ORG_ID, OPENAI_PROJECT_ID) belong to your OpenAI account. Unset them for the new client so they aren’t sent to a provider that doesn’t know them.
Step 6: roll it out gradually
Because the switch is just configuration, you can roll it out one environment at a time:
- Local and staging first. Run your test suite and a small set of real prompts. Compare quality, latency and the
usagenumbers with what you had before. - Production behind a flag. Keep the old values in the deployment config and switch with an environment variable, so rolling back is a redeploy, not a code change.
- Watch the first day. Track error rates by status code and compare spend in the usage log with your estimate.
If you also use coding agents, the same base URL idea applies to them. The Claude Code setup guide and the agent kits cover tool-specific settings.
Checklist
- The base URL includes
/v1and nothing after it. - The key is loaded from a project-level secret, not a shell profile.
curlto/modelsand/chat/completionsboth work with that key.- Every model ID in code and config exists on the new endpoint.
- OpenAI-only endpoints stay on a separate client.
- The rollout can be reversed by changing configuration.