OpenAI SDK base_url Setup for Türkiye: Switch to 769+ Models with One Line

Updated: 8 min read

To point your OpenAI SDK at multiple models from Türkiye, set base_url to https://api.onysoft.com/v1 and supply an sk-ony- prefixed key; the rest of your code stays unchanged. The same switch works with zero code changes via the OPENAI_BASE_URL environment variable, and writing a provider/model ID in the model field unlocks 769+ models on one prepaid balance.

You have a working codebase built on the OpenAI SDK — but you also want Claude's reasoning, Gemini's long context, or DeepSeek's price-performance ratio. The traditional path means a separate SDK, separate keys, and a separate error format per provider. This guide covers the whole migration: the one-line change in Python, Node.js, and curl, the environment-variable method, a table of how each SDK version accepts base_url, the response-format difference between streaming and non-streaming calls, and a pre-production checklist.

The gateway is operated by Onysoft Veri Merkezi A.S., a datacenter company based in Izmir, Türkiye: you top up a balance in lira, usage is deducted at the TCMB central-bank rate, and corporate e-invoices are issued — no foreign card or VPN needed. This article also carries data you will not find elsewhere for this query: real response times measured across 6,959 requests in the 14 days to August 5, 2026, the per-response cost field in action, and a priced scenario with lira equivalents.

What Exactly Does the base_url Change Do?

base_url is the root address the official OpenAI SDKs send every request to; change this single value to https://api.onysoft.com/v1 and your existing code starts talking to 769+ models — Claude, GPT, Gemini, and DeepSeek included — through Onysoft AI Gateway, on the same request and response schema. The SDK itself, your request bodies, your error handling: none of it changes.

Why would you want this? Wiring a separate SDK per provider looks harmless on day one and gets expensive by month six; as the codebase grows, vendor lock-in accumulates silently, and switching models stops being a config change and becomes a refactoring project. A single compatible endpoint inverts that equation:

  • Lock-in broken: when a better model ships, the switching cost is near zero — update the model ID, redeploy.
  • A/B model testing: run the same prompt through anthropic/claude-sonnet-5 and openai/gpt-5.6-terra in parallel and compare quality against spend; the per-response cost field hands you the spend side for free.
  • Fallbacks: a few-line retry layer that reroutes to a second model when the primary errors out is trivial when both live behind the same API.

Operating from Türkiye adds one more layer to the equation: a lira balance, corporate e-invoicing, and a KVKK counterparty under Turkish law. That layer is detailed in the Türkiye LLM Gateway guide, with the broader picture in the AI API guide.

Python, Node.js, and curl: the One-Line Change in Three Languages

In all three languages, the only thing that changes is the line where the client is constructed. In Python (official openai package):

from openai import OpenAI

client = OpenAI(
    base_url="https://api.onysoft.com/v1",  # the only line that changes
    api_key="sk-ony-YOUR_KEY",
)

resp = client.chat.completions.create(
    model="anthropic/claude-sonnet-5",
    messages=[{"role": "user", "content": "Introduce yourself in one sentence."}],
)
print(resp.choices[0].message.content)

In Node.js/TypeScript (official openai package, v4+):

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.onysoft.com/v1", // the only line that changes
  apiKey: "sk-ony-YOUR_KEY",
});

const resp = await client.chat.completions.create({
  model: "openai/gpt-5.6-luna",
  messages: [{ role: "user", content: "One API, many models. Summarize?" }],
});
console.log(resp.choices[0].message.content);

Without any SDK, straight from curl:

curl https://api.onysoft.com/v1/chat/completions \
  -H "Authorization: Bearer sk-ony-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemini-3.6-flash",
    "messages": [{"role": "user", "content": "Summarize this support ticket in one sentence."}]
  }'

On the PHP side, the community-standard openai-php/client package does the same job with OpenAI::factory()->withBaseUri('https://api.onysoft.com/v1'). Every model is addressed as provider/model (anthropic/claude-opus-5, deepseek/deepseek-v4-flash, and so on); pull the current ID list programmatically from GET /v1/models, or try a model without touching code in the Playground.

Migrating Without Touching the Codebase: the OPENAI_BASE_URL Environment Variable

The official Python (openai ≥ 1.0) and Node.js (openai ≥ 4.0) SDKs read base_url from the OPENAI_BASE_URL environment variable when no constructor parameter is given — meaning you can migrate without writing a single line of code:

export OPENAI_BASE_URL="https://api.onysoft.com/v1"
export OPENAI_API_KEY="sk-ony-YOUR_KEY"
# If your code constructs OpenAI() / new OpenAI() with no arguments,
# nothing else needs to change.

This method has two practical advantages: rollback is also a one-variable job (set it back, redeploy), and routing staging and production to different endpoints stays inside CI/CD configuration and never enters code review. Which path applies to which version:

SDK / packageVersionbase_url in codeEnvironment variable
openai (Python)≥ 1.0OpenAI(base_url=...)OPENAI_BASE_URL
openai (Python, legacy)0.xopenai.api_base = "..."OPENAI_API_BASE
openai (Node.js)≥ 4.0new OpenAI({ baseURL: ... })OPENAI_BASE_URL
openai (Node.js, legacy)3.xnew Configuration({ basePath: ... })—
openai-php/client (PHP)allwithBaseUri(...)— (read the variable yourself)

Verified: August 5, 2026 — official SDK documentation.

The 0.x Python and 3.x Node lines have been unmaintained for years; treat the migration as an opportunity to move to the current major version — every example above targets the current majors.

Streaming and the cost Field: How the Response Format Differs Between the Two Modes

The rule is simple: with streaming off, the OpenAI fields (id, choices, usage) are at the top level of the response; for backward compatibility the response also carries "success": true and the same content under data. The request's charge appears in the cost.amount field in USD (the same value is also under data.cost.amount). The raw JSON body of a non-streaming call looks like this:

{
  "id": "req_...",
  "object": "chat.completion",
  "model": "openai/gpt-5.6-luna",
  "choices": [ ... ],
  "usage": { "prompt_tokens": 12, "completion_tokens": 84, "total_tokens": 96 },
  "cost": { "amount": 0.000155, "currency": "USD" },
  "success": true,
  "data": { ... the same fields as at the top level ... }
}

The official SDKs and libraries such as LangChain read the top level, so resp.choices[0].message.content works directly. If you work with curl or your own HTTP client, read the content from .choices[0].message.content and the charge from .cost.amount (| jq '.cost.amount'). Existing integrations that read .data.choices and .data.cost.amount keep working unchanged; use the top level in new integrations. That cost field is one of the gateway's distinguishing features: instead of totaling spend from the dashboard at the end of the day, you can log each request's charge from the response itself and run quality-versus-cost A/B comparisons per request.

With streaming on, chunks arrive in the raw OpenAI SSE format and end with data: [DONE]. The one addition: in the last chunk choices is an empty array, and that chunk carries the request's usage and cost. So check in your loop that choices may be empty:

stream = client.chat.completions.create(
    model="anthropic/claude-sonnet-5",
    messages=[{"role": "user", "content": "Explain microservice architecture."}],
    stream=True,
)

for chunk in stream:
    if chunk.choices:  # the last chunk has empty choices (usage and cost are there)
        delta = chunk.choices[0].delta.content
        if delta:
            print(delta, end="", flush=True)

You can record the cost of a streamed request from the cost.amount field in the last chunk, and follow total spend in the dashboard usage breakdown as well.

How It Works on Onysoft: Prices, Real Latency, and OnyRouter

After the migration, what sets your invoice is no longer code — it is the string you write in the model field. Current sale prices from the catalog show the spread between the extremes:

ModelInput ($/1M tokens)Output ($/1M tokens)Input (TRY/1M)Output (TRY/1M)
anthropic/claude-opus-5$7.50$37.50₺363.71₺1,818.53
anthropic/claude-sonnet-5$3.00$15.00₺145.48₺727.41
openai/gpt-5.6-terra$3.00$18.00₺145.48₺872.89
openai/gpt-5.6-luna$0.30$1.80₺14.55₺87.29
google/gemini-3.6-flash$1.125$5.625₺54.56₺272.78
deepseek/deepseek-v4-flash$0.0735$0.147₺3.56₺7.13

Measured: September 13, 2026 — api.onysoft.com live catalog. TRY equivalents calculated at the TCMB rate (1 USD = 48.4941 TRY; September 11, 2026). Prices are Onysoft sale prices as of September 13, 2026; see /models for current pricing.

A concrete scenario: a chatbot generating 10M input + 2.5M output tokens per month costs $67.50 (₺3,273.35) on anthropic/claude-sonnet-5, $7.50 (₺363.71) on openai/gpt-5.6-luna, and $1.10 (₺53.46) on deepseek/deepseek-v4-flash — and switching between the three is exactly the one-string change this article is about. Run your own profile through the cost calculator.

On speed, here is measurement rather than estimation: across 6,959 successful requests through the gateway in the 14 days to August 5, 2026, average end-to-end response time was 3.8 seconds, with the fastest at 0.3 seconds. What pulls the average up is not the gateway but long-generation flagship and reasoning requests in the traffic; the 0.3-second floor shows how thin the layer is with lightweight models.

If you would rather delegate model selection to the gateway, OnyRouter is built in:

resp = client.chat.completions.create(
    model="onysoft/auto",  # OnyRouter: the gateway picks the best-fit model
    messages=[{"role": "user", "content": "Optimize this SQL query."}],
)
print(resp.model)  # the selected model is reported transparently

Routing itself is free; only the selected model's usage is billed. Full details in the OnyRouter guide.

Migration Checklist: 401, 404, 402, and Honest Limits

Work through this list before going to production; the first three items alone close most migration errors:

  1. Key (401 Unauthorized): an old OpenAI key (sk-proj-...) may still sit in your environment. Onysoft keys start with sk-ony- and must travel in the Authorization: Bearer header — the SDKs handle that automatically.
  2. Endpoint (404): the trailing /v1 on base_url is frequently dropped. The correct value is https://api.onysoft.com/v1.
  3. Model ID (400 / unsupported_model): the provider prefix is mandatory: anthropic/claude-sonnet-5, not claude-sonnet-5. Take IDs from GET /v1/models or the catalog's copy button.
  4. Balance and 402: before every request the gateway compares the estimated cost, padded by a 20% safety margin (×1.2), against your balance; if it falls short, the request returns 402 without ever reaching the model. Handle 402 separately in your retry layer — the fix is a top-up, not a retry.
  5. Per-key limits: issue separate keys for production and testing, and set per-key, per-model daily/monthly token and request limits from the dashboard so a runaway loop cannot drain the budget.
  6. A note on embeddings: the gateway also exposes an OpenAI-compatible /v1/embeddings endpoint (openai/text-embedding-3-small, openai/text-embedding-3-large). Keeping embedding traffic with your current provider is still a valid choice in a RAG setup — the two workloads can live on different keys and cost profiles.
  7. Coming from the Anthropic SDK: no conversion needed. The gateway also serves the Anthropic Messages API (/v1/messages): give the Anthropic client base_url="https://api.onysoft.com" (do not append /v1) and your sk-ony- key; the model field takes the catalog id (anthropic/claude-sonnet-5.5) or the Anthropic id (claude-sonnet-5-5). Files API file ids, Message Batches, thread, server-side code execution and remote mcp_servers are not supported; details in the Anthropic Messages API section.
  8. Image/video/music: these models run on asynchronous endpoints: POST /v1/video/generate returns a task_id, polled via GET /v1/video/status/{task_id}; a plain HTTP client is enough — no OpenAI SDK involved.

For anything beyond the list, the error-code section of the API documentation and 24/7 support have you covered. To start, open a free account and fire your first request from the Playground.

Last updated: September 13, 2026 · Data: api.onysoft.com live catalog

Frequently Asked Questions

How do I change base_url in the OpenAI SDK?

In Python, write OpenAI(base_url="https://api.onysoft.com/v1", api_key="sk-ony-..."); in Node.js, pass the same values as baseURL and apiKey. If you prefer not to touch code, set the OPENAI_BASE_URL and OPENAI_API_KEY environment variables — current SDKs read them automatically when no constructor parameters are given.

Which SDK versions support the OPENAI_BASE_URL environment variable?

The official Python SDK from 1.0 onward and the Node.js SDK from 4.0 onward read OPENAI_BASE_URL. The legacy Python 0.x line uses the OPENAI_API_BASE variable and the openai.api_base assignment, while Node 3.x accepts basePath in code only. We recommend upgrading from these unmaintained lines to the current majors.

Does my existing OpenAI code need any other changes?

No. Request bodies and streaming are compatible with the OpenAI chat/completions schema; the only difference is writing a provider/model ID in the model field (for example anthropic/claude-sonnet-5). The current ID list can also be fetched programmatically from GET /v1/models.

Are streaming responses byte-for-byte OpenAI format?

Yes — with streaming on, chunks arrive in the raw OpenAI SSE format and end with data: [DONE]. The one addition: in the last chunk choices is an empty array, and that chunk carries the request's usage and cost; check in your loop that choices may be empty. With streaming off, the OpenAI fields are at the top level of the response too; for backward compatibility it also carries success: true and the same content under data, so your existing code works whether you use an SDK or raw HTTP.

How do I see the cost of a single request?

Every non-streaming response carries a cost.amount field (and data.cost.amount for backward compatibility) with that request's charge in USD; log it and you have per-request cost tracking inside your application. For streamed requests the same cost field arrives in the last chunk, and total spend also appears in the dashboard usage breakdown. For monthly projections, use the calculator on the /calculator page.

What happens if my balance is insufficient?

Before every request, the gateway compares the estimated cost padded by a 20% safety margin (×1.2) against your balance; if it falls short, the request returns a 402 error without ever reaching the model. That prevents mid-generation cutoffs and surprise negative balances. Handle 402 separately in your retry layer: the fix is topping up from the dashboard — the balance takes effect immediately — not retrying.

Share this article

Share on X LinkedIn WhatsApp

Related pages

AI API Guide → Türkiye LLM Gateway Guide → OnyRouter: Automatic Model Selection → Full catalog of 769+ models with current pricing →

Ready to build?

Access 769+ AI models through a single API. Pay as you go — no subscription.

Create Free Account Browse Models

← All posts

Want help finding the right model?