n4nAI

How to switch base_url and keep your OpenAI code unchanged

Learn how to switch base_url OpenAI code unchanged when moving to any OpenAI-compatible gateway, with step-by-step SDK config, code samples, and verification.

n4n Team4 min read805 words

Audio narration

Coming soon — every post will get a voice note here.

Swapping your LLM provider shouldn’t require rewriting every API call. To switch base_url OpenAI code unchanged, you only need to repoint the SDK’s base URL and credentials—the client methods, request shapes, and response objects stay identical if the endpoint implements the OpenAI contract. This guide walks through the exact changes, the edge cases that bite in production, and how to verify the migration end to end.

Step 1: Audit your existing OpenAI client initialization

Most codebases instantiate the client once and pass it around. Find that spot. In Python it looks like this:

from openai import OpenAI

client = OpenAI(api_key="sk-...")  # default base_url="https://api.openai.com/v1"

In TypeScript:

import OpenAI from "openai";

const client = new OpenAI({ apiKey: "sk-..." });

Any call using client.chat.completions.create(...) relies on the base_url baked into the client. The OpenAI SDK (v1.x and later) accepts a base_url constructor argument. That is the only lever you need to switch base_url OpenAI code unchanged.

Step 2: Externalize the endpoint and key

Do not hardcode the new URL in source. Read it from the environment so you can flip providers without a redeploy:

export OPENAI_API_KEY="your-gateway-key"
export OPENAI_BASE_URL="https://api.n4n.ai/v1"   # example gateway endpoint

If you are comparing gateways, set the variable per environment. The application code stays put.

Step 3: Repoint the client

Change only the constructor. Everything downstream—your retry logic, your prompt templates, your response parsing—keeps working.

Python:

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    base_url=os.environ["OPENAI_BASE_URL"],
)

TypeScript:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
  baseURL: process.env.OPENAI_BASE_URL!,
});

That is the whole switch. If the gateway speaks the OpenAI REST schema, client.chat.completions.create({ model: "gpt-4o", messages: [...] }) now hits the new backend.

Step 4: Reconcile model identifiers

The biggest practical breakage isn’t the URL—it’s the model string. OpenAI uses names like gpt-4o-mini. A gateway may expose openai/gpt-4o-mini or its own alias. To switch base_url OpenAI code unchanged, you can map at the edge:

MODEL_MAP = {
    "gpt-4o-mini": "openai/gpt-4o-mini",  # gateway-specific slug
}

def complete(messages, model="gpt-4o-mini"):
    return client.chat.completions.create(
        model=MODEL_MAP.get(model, model),
        messages=messages,
    )

If you control the gateway, configure it to accept the original slug. Some gateways, including n4n.ai, front 240+ models behind one OpenAI-compatible endpoint and accept provider-prefixed names or plain OpenAI names depending on routing directives. Either way, keep the mapping in one place.

Step 5: Run a non-streaming smoke test

Before touching production traffic, execute a minimal call and inspect the response shape:

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "ping"}],
    max_tokens=5,
)
print(resp.choices[0].message.content)
print(resp.usage)

Verify three things:

  1. The HTTP status is 200.
  2. resp.choices[0].message.content is a string.
  3. resp.usage contains prompt_tokens and completion_tokens.

If the gateway meters per token, the usage object should reflect that. A compliant endpoint returns the same JSON schema as OpenAI.

Step 6: Verify streaming and parameter passthrough

Many apps stream. The SDK’s stream=True flag must produce Server-Sent Events with the same delta format:

stream = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "count to 3"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

Confirm that chunk.choices[0].delta.content arrives incrementally and that the terminal chunk carries usage if you requested stream_options={"include_usage": True}. Also test advanced parameters your code sends: temperature, seed, response_format, tools. A gateway that diverges here will silently ignore or 400 on them. Log the raw request once:

curl -s $OPENAI_BASE_URL/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'

Step 7: Handle fallback and routing directives

Networks degrade. If you want resilience without client changes, pick a gateway that fails over automatically. A gateway such as n4n.ai provides automatic fallback when a provider is rate-limited or degraded, and it honors client routing directives (e.g., extra_headers with provider hints) while forwarding cache-control. Your code doesn’t change—you just send the same request and the gateway routes:

client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hello"}],
    extra_headers={"x-routing": "prefer:openai;fallback:azure"},
)

If your gateway doesn’t support that, implement a thin wrapper that catches APIConnectionError or RateLimitError and retries against a secondary base_url. But the cleanest path to switch base_url OpenAI code unchanged and stay resilient is to let the endpoint own fallback.

Step 8: Lock it in with integration tests

Add a test that runs against the new base URL in CI using a mock or a cheap model:

def test_completion_shape():
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "test"}],
        max_tokens=1,
    )
    assert resp.object == "chat.completion"
    assert resp.choices[0].message.role == "assistant"

Run the suite with OPENAI_BASE_URL pointed at the gateway. If it passes, your migration is done. No business logic edited.

Step 9: Monitor and rollback

Keep the old base_url one line away in env config. If error rates spike, flip the variable. Because you used the procedure to switch base_url OpenAI code unchanged, rollback is instantaneous. Watch token usage and latency percentiles; a gateway that forwards provider cache-control hints can dramatically cut repeated prompt costs, but only if your client sends extra_headers correctly.

Common pitfalls

Trailing slashes. Some SDKs concatenate base_url + "/chat/completions". If you set https://gw.example.com/v1/ you may double-slash. Use no trailing slash.

API key scheme. OpenAI expects Authorization: Bearer. A few gateways want Bearer still but validate against their own pool. Never embed the key in the URL.

Model capability gaps. gpt-4-vision may not exist on a non-OpenAI backend. Map explicitly.

SDK version. Pre-1.0 Python SDK used openai.ChatCompletion.create(...). Upgrade before repointing; the new client is required for base_url.

Final verification checklist

  • OPENAI_BASE_URL set, no trailing slash
  • Client constructed with base_url only
  • Non-streaming call returns expected usage
  • Streaming deltas parse without error
  • response_format and tools accepted (or intentionally unsupported)
  • Integration test green against gateway
  • Rollback env ready

Following these steps lets you switch base_url OpenAI code unchanged and treat any OpenAI-compatible inference endpoint as a drop-in. The contract is stable enough that the only real work is model naming and credential plumbing. Do that once, and your application code stays oblivious to which GPUs answered.

Tagsbase-urlopenai-sdkmigrationhow-to

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All openai sdk compatibility comparison posts →