Skip to content

Use the official OpenAI SDKs for Python, Node, Go and Java with Router One

The official OpenAI SDKs, the openai package on PyPI and on npm, work with Router One once the client gets two values: the base URL https://api.router.one/v1 and a Router One key. client.chat.completions.create then reaches every chat model in the catalog (Claude, Gemini, DeepSeek and Grok IDs as well as GPT), with a cost and latency trace per request. The rest of the SDK does not carry over wholesale: client.responses.create works only for IDs whose model page lists POST /v1/responses, and embeddings, audio, files and batches have no route on the gateway. This guide covers install and version floors, the environment variables each SDK reads, a Python and a Node example, short Go and Java sections, which SDK method reaches which endpoint, streaming usage, Claude's thinking output, image generation, the HTTP 200 error body on long non-streaming calls, retries, and how to see which URL the SDK actually called. Checked against openai 3.19.2 (Python) and openai 7.23.0 (Node) on 2026-09-27 and against openai-go v3.66.0 and openai-java 4.70.0 on 2026-09-29, per each SDK's docs and source, and re-checked on 2026-10-06 against openai 3.24.0 (Python), openai 7.28.0 (Node), openai-go v3.71.2 and openai-java 4.76.0, whose release notes list no change to the settings and behavior this guide describes.

Install the SDK and check the version floors

Python: pip install -U openai installs openai 3.x, which requires Python 3.10 or newer. Version 3.0.0 (2026-08-12) moved the SDK's HTTP layer to HTTPX2, so a plain install no longer brings httpx or certifi with it; if your own code imports httpx, declare it as a dependency of your project. Node.js: npm install openai installs openai 7.x, which requires Node.js 22 or later (the SDK's README names Node.js 22 as the minimum supported version), and every JavaScript example on this page assumes Node.js 22+. Both SDKs read two environment variables when the client is built without explicit values: OPENAI_BASE_URL for the base URL and OPENAI_API_KEY for the key. Setting them as below makes a bare OpenAI() or new OpenAI() talk to Router One without code changes. Neither SDK reads OPENAI_API_BASE. That older name is still read by some frameworks (LangChain checks it before OPENAI_BASE_URL), so a tutorial that sets only OPENAI_API_BASE leaves the SDK on api.openai.com, which does not accept a Router One key. The examples on this page pass both values explicitly and read the key from ROUTER_ONE_API_KEY, a name this guide chooses, so an OPENAI_* variable left over from another project cannot redirect them. Create a separate key for each application in the dashboard and give it a maxSpend cap.

environment
# Read by both SDKs when the client gets no base URL or key of its own
OPENAI_BASE_URL=https://api.router.one/v1
OPENAI_API_KEY=sk-your-router-one-key

Configure OpenAI SDK to use the Router One base URL

Save this as openai-sdk-example.py and run it after exporting ROUTER_ONE_API_KEY. base_url must end in exactly one /v1, because the SDK appends /chat/completions, /responses or /models to it. Without /v1 the gateway answers HTTP 404 with a message saying OpenAI-compatible clients need a base_url ending in /v1, and a /v1/v1 path means /v1 was written twice. The key is sent as Authorization: Bearer. The first call uses Chat Completions, which serves every chat model in the catalog, so the same code runs with anthropic/claude-sonnet-5, google/gemini-3.7-flash, deepseek-v4.1-flash, grok-4.7 or openai/gpt-5.5. Copy the ID from /models exactly, vendor prefix included where it has one: the SDK does not check model names, so a mistyped ID travels to the gateway and fails there. max_tokens sets the output cap; Router One's Chat Completions also accepts max_completion_tokens, the newer name the SDK offers, with the same meaning (if both are sent, max_completion_tokens wins). The second call uses the Responses API, which works only for IDs whose model page lists POST /v1/responses (currently the GPT-family models, DeepSeek IDs and Grok chat models), so it uses openai/gpt-5.5. A Claude-family ID sent to client.responses.create is rejected with HTTP 400 before any model is called.

openai-sdk-example.py
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.router.one/v1",
    api_key=os.environ["ROUTER_ONE_API_KEY"],
)

# Chat Completions: every chat model in the catalog
chat = client.chat.completions.create(
    model="anthropic/claude-sonnet-5",
    messages=[{"role": "user", "content": "Hello!"}],
    max_tokens=512,
)
print(chat.choices[0].message.content)

# Responses: only IDs whose model page lists POST /v1/responses
resp = client.responses.create(model="openai/gpt-5.5", input="Hello!")
print(resp.output_text)

The same client in Node.js

The Node SDK takes the same two values under camelCase names, baseURL and apiKey, and returns the same response shapes. Save this as router-one.mjs and run node router-one.mjs on Node.js 22 or later; the .mjs extension makes the file an ES module, which top-level await needs. The first request is a plain chat call to deepseek-v4.1-flash. The second streams from anthropic/claude-sonnet-5 with stream_options.include_usage, so the stream ends with a chunk that carries a usage object and an empty choices array, which is why the loop reads choices[0] with optional chaining. Like the Python SDK, the Node SDK retries a failed request twice by default and times out after 10 minutes; set maxRetries and timeout on the constructor or per request. In TypeScript, fields the SDK does not type, such as thinking in a request or reasoning_content in a response, need a cast, but the SDK sends extra request fields exactly as written.

router-one.mjs
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.router.one/v1",
  apiKey: process.env.ROUTER_ONE_API_KEY,
});

const chat = await client.chat.completions.create({
  model: "deepseek-v4.1-flash",
  messages: [{ role: "user", content: "Hello!" }],
  max_tokens: 512,
});
console.log(chat.choices[0]?.message.content);

// Streaming: the last chunk has empty choices and the usage object
const stream = await client.chat.completions.create({
  model: "anthropic/claude-sonnet-5",
  messages: [{ role: "user", content: "Count to five." }],
  stream: true,
  stream_options: { include_usage: true },
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
  if (chunk.usage) console.log(chunk.usage);
}

Go: openai-go with option.WithBaseURL

openai-go, the official Go SDK (github.com/openai/openai-go/v3), was at v3.71.2 on October 5, 2026, and its releases from v3.45.0 on need Go 1.25 or newer. openai.NewClient reads OPENAI_BASE_URL and OPENAI_API_KEY from the environment and then applies the options you pass, so option.WithBaseURL and option.WithAPIKey in the example override a stray variable; like the Python and Node SDKs, it does not read OPENAI_API_BASE. client.Chat.Completions.New reaches every chat model in the catalog, and Model takes the catalog ID as a plain string, vendor prefix included. MaxTokens is sent as max_tokens and MaxCompletionTokens as max_completion_tokens; Router One's Chat Completions accepts either as the output cap (if both are set, max_completion_tokens wins). The SDK retries connection errors and HTTP 408, 409, 429 and 5xx twice by default (option.WithMaxRetries changes that) and sets no timeout of its own, so give long calls a context deadline, or stream them with client.Chat.Completions.NewStreaming. client.Responses.New works only for IDs whose model page lists POST /v1/responses.

main.go
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/openai/openai-go/v3"
	"github.com/openai/openai-go/v3/option"
)

func main() {
	client := openai.NewClient(
		option.WithBaseURL("https://api.router.one/v1"),
		option.WithAPIKey(os.Getenv("ROUTER_ONE_API_KEY")),
	)
	chat, err := client.Chat.Completions.New(context.TODO(), openai.ChatCompletionNewParams{
		Model:     "anthropic/claude-sonnet-5",
		Messages:  []openai.ChatCompletionMessageParamUnion{openai.UserMessage("Hello!")},
		MaxTokens: openai.Int(512),
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(chat.Choices[0].Message.Content)
}

Java: openai-java with baseUrl

openai-java 4.76.0 (October 4, 2026) runs on Java 8 or later. OpenAIOkHttpClient.fromEnv() reads OPENAI_BASE_URL and OPENAI_API_KEY, or the openai.baseUrl and openai.apiKey system properties, which take precedence over the variables; the builder's baseUrl and apiKey set the values in code, as below, and like the other SDKs it does not read OPENAI_API_BASE. model accepts the catalog ID as a string. maxTokens sends max_tokens, which the SDK marks deprecated in favor of maxCompletionTokens; Router One's Chat Completions accepts either as the output cap, so you can switch to maxCompletionTokens (if both are set, max_completion_tokens wins). The client retries connection errors and HTTP 408, 409, 429 and 5xx twice by default and times out after 10 minutes; maxRetries and timeout on the builder change both, and OPENAI_LOG=debug turns on the SDK's request logging. The README advises one client per application, because each client holds its own connection and thread pools. Its openai-java-spring-boot-starter artifact ended at 4.45.0, so a Spring application builds this client as a bean, or uses Spring AI.

RouterOne.java
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;

public class RouterOne {
    public static void main(String[] args) {
        OpenAIClient client = OpenAIOkHttpClient.builder()
                .baseUrl("https://api.router.one/v1")
                .apiKey(System.getenv("ROUTER_ONE_API_KEY"))
                .build();

        ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
                .model("anthropic/claude-sonnet-5")
                .addUserMessage("Hello!")
                .maxTokens(512)
                .build();
        ChatCompletion chat = client.chat().completions().create(params);
        System.out.println(chat.choices().get(0).message().content().orElse(""));
    }
}

Which SDK method reaches which Router One endpoint

The SDKs cover far more of OpenAI's API than a gateway serves. Router One serves Chat Completions, Responses, Anthropic Messages, the model list, the wallet balance, image generation and editing, and video generation, all under /v1; any other path has no route. Check each method your code uses against this table before moving it over:

Which SDK method reaches which Router One endpoint
SDK call (Python and Node)Router One endpointWhat works
chat.completions.createPOST /v1/chat/completionsEvery chat model in the catalog; tools, streaming and image input as listed on each model page
responses.createPOST /v1/responsesOnly IDs whose model page lists this endpoint: GPT-family IDs such as openai/gpt-5.5, azure/gpt-6-astra and codex-auto-review, deepseek-v4.1-flash, deepseek-v4-flash and Grok chat models; background: true and the image_generation tool are rejected with 400
models.listGET /v1/modelsThe current catalog's model IDs, the same IDs the /models page shows
images.generate, images.editPOST /v1/images/generations, POST /v1/images/editsImage model IDs such as gpt-image-2 and gemini-3.1-flash-image-preview, billed per image; edits upload reference images as multipart form data
embeddings, audio, files, batches, vector_stores, conversations, responses.retrieveNo routeHTTP 404 with a valid key (a key the gateway does not accept gets 401 first); keep these calls on a provider that serves them

Stream with usage and read Claude's thinking

Streaming works on both endpoints. On Chat Completions, add stream_options with include_usage so the last chunk of the stream carries token usage; that chunk has an empty choices list, so check chunk.choices before indexing it. Without include_usage the stream carries no usage at all, although Dashboard → Logs still records the request's tokens and cost. For a streamed request that produced output, Logs also shows time to first token (TTFT) next to the total time. The gap between the first chunk your code measured and the logged TTFT is your network, your client and the gateway's request intake; a long TTFT itself points at queueing, a failed attempt before failover, or the model's own wait. Thinking output from Claude arrives in a non-standard reasoning_content field on the message, or on each stream delta. The catalog IDs that end in -thinking (anthropic/claude-sonnet-4.6-thinking, anthropic/claude-opus-4.7-thinking and anthropic/claude-opus-4.6-thinking) get thinking by default, so no request field is needed for them. The SDKs do not type reasoning_content: in Python read it with getattr, as below, or from model_extra; in Node read message.reasoning_content through a cast. To send a thinking object yourself, note that Python's create() has no thinking parameter and raises TypeError on one, so pass extra_body={"thinking": {...}}; the Node SDK sends the field as written. Which thinking settings a model accepts is decided by the model. For Claude-family IDs, reasoning_effort does not become a thinking setting; the one exception is Claude Opus 5.5, where the gateway maps it to that model's effort setting. Thinking tokens are billed at the model's output rate.

stream_thinking.py
stream = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.6-thinking",
    messages=[{"role": "user", "content": "Is 221 prime?"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices:
        delta = chunk.choices[0].delta
        thinking = getattr(delta, "reasoning_content", None)
        if thinking:
            print(thinking, end="", flush=True)
        if delta.content:
            print(delta.content, end="", flush=True)
    elif chunk.usage:
        print(chunk.usage)

Long non-streaming calls can return HTTP 200 with an error

A stream: false call that runs longer than about 25 seconds gets HTTP 200 and JSON whitespace from the gateway while the answer is prepared, so the connection is not dropped while it waits for a first byte. If the call fails after that point, the status code can no longer change: the failure arrives as HTTP 200 with a top-level error object instead of choices. Neither SDK raises on it. Python returns a ChatCompletion whose choices is None, with the error in model_extra; Node returns an object whose choices is undefined and whose error field holds the gateway's message. Reading choices[0] then fails with an unrelated TypeError far from the cause. For reasoning models and other long generations, stream the response, or check for an error before reading choices, as below. Non-streaming calls to /v1/responses behave the same way, so check them for an error field too. Calls that finish or fail before the keepalive starts keep ordinary status codes, and the SDKs raise their usual error classes for those.

long_call.py
completion = client.chat.completions.create(
    model="openai/gpt-5.5",
    messages=[{"role": "user", "content": "Plan a 12-step data migration."}],
)
error = (completion.model_extra or {}).get("error")
if error or not completion.choices:
    raise RuntimeError(f"Router One returned an error body: {error}")
print(completion.choices[0].message.content)

Retries and timeouts stack on the gateway's failover

Both SDKs retry a failed request twice by default, on connection errors and on HTTP 408, 409, 429 and 5xx, waiting as the Retry-After header asks, and both time out after 10 minutes. The gateway has already done its own failover before it answers: requests are served by the model you name, and a route that returns 429, 5xx or times out is retried on another route for the same model. A 429 or 5xx that reaches your code has therefore been through that step, and each SDK retry is a new request to the gateway with its own request_id. A 429 from Router One's own limits on the key or account is refused before any model is called and carries Retry-After and an X-RateLimit-Scope of api_key, subject or subject_model, while a 429 that has a row in Dashboard → Logs came back from upstream after the gateway's retries; the error-codes page explains how to tell the two apart. For batch jobs, lower max_retries (Python) or maxRetries (Node) so one failing input does not turn into three attempts; for a single call use client.with_options(max_retries=0) in Python, or pass maxRetries in the request options in Node. Give long generations stream=True rather than a longer timeout.

Which model ID should OpenAI SDK send?

Copy the exact model ID from /models, preserving case, hyphens, and version suffixes; do not substitute a display name. Open its detail page and match the supported API endpoints, context window, and capabilities such as tool calling to the provider and features selected in OpenAI SDK. A catalog listing does not mean the client can use every feature of that model. Give each client or application a dedicated API key with a maxSpend cap.

Which API protocol is OpenAI SDK using?

OpenAI-compatible describes an interface format; it does not make Chat Completions (/v1/chat/completions), Responses (/v1/responses), and Anthropic Messages (/v1/messages) interchangeable. Check the installed client version, provider configuration, and actual request path against the model detail page and API compatibility fact sheet. A successful plain-text chat does not establish support for hosted tools, conversation state, or file-editing features.

Verify the OpenAI SDK call in your request trace

Send a simple text request from OpenAI SDK, then match its trace in Dashboard → Logs by time, model, and request_id: tokens, cost, latency, and status. Next, test streaming, tool calls, and multi-turn history separately. For failures, retain the actual request path, full error message, and request_id. If there is no matching log, either the request never reached the gateway (client configuration or connectivity) or the gateway refused it before calling a model — a 401 for the key, a 402 for funds, a 429 from your key's or account's limits, or a 400 or 404 for the model ID or endpoint; a streamed request that failed before any output may have no log either. The status and error message the client received tell which, so check them before attributing the error to the gateway or upstream.

FAQ

Is moving to Router One only a base URL change?

For chat.completions.create, yes: set the base URL and the key, and put an exact catalog ID in model. Three things fall outside that switch. responses.create works only for IDs whose model page lists POST /v1/responses; a Claude-family ID sent there returns HTTP 400 with model '<id>' must be called via /v1/messages or /v1/chat/completions, so keep Claude, Gemini and other IDs on chat.completions.create. Non-standard fields, such as thinking in a request and reasoning_content in a response, travel through extra_body and model_extra in Python and through a cast in Node. And endpoints the gateway does not serve, such as embeddings, audio and files, return 404, so code that uses them keeps a separate client for another provider.

Can I keep using client.responses.create?

Yes, with an ID whose model page lists POST /v1/responses: currently the GPT-family models (for example openai/gpt-5.5, openai/gpt-5.6-sol, azure/gpt-6-astra and codex-auto-review), deepseek-v4.1-flash and deepseek-v4-flash, and Grok chat models such as grok-4.7. On those IDs the gateway accepts text and multi-part input, instructions, streaming, function tools, custom tools, and previous_response_id, conversation and prompt references. Hosted tool fields such as web_search are accepted and metered, but Router One does not execute tools itself. background: true and the image_generation tool are rejected with HTTP 400; generate images with images.generate instead. Only Claude-family IDs get the fixed must be called via message on this endpoint, so for any other ID check its model page before relying on Responses.

Can the same client generate images?

Yes. client.images.generate(model="gpt-image-2", prompt="...") posts to /v1/images/generations with the same key, and the other image IDs in the catalog (gpt-image-2.5-flare, gpt-image-2.5-sunburst, gemini-3-pro-image-preview, gemini-3.1-flash-image-preview, gemini-nano-banana-2.1, grok-imagine-image and grok-imagine-image-quality) take the same call. Each image is billed at the model's flat per-image price shown on its model page, not by tokens. An image can come back as data[].b64_json or as data[].url, so handle both and save the bytes right away. client.images.edit uploads reference images as multipart form data to /v1/images/edits, for image models that accept image input.

Do embeddings, audio or the Files API work through Router One?

No. Router One serves chat, Responses, the model list, the balance, and image and video generation; client.embeddings.create, audio transcription and speech, files, batches, vector stores and conversations have no route and return HTTP 404. A RAG pipeline keeps its embedder on a provider that offers embeddings, with that provider's key, and sends only its chat calls to the gateway.

How do I see which URL the SDK actually called?

Dashboard → Logs records each request's model, API key, tokens, cost, total time and status, not its path, so look at the path on the client side. In Node, set OPENAI_LOG=debug (or logLevel: 'debug' on the client) and the SDK logs each request's method and URL. The Python SDK does not log URLs; give the client an HTTP client with an event hook: OpenAI(base_url=..., api_key=..., http_client=DefaultHttpx2Client(event_hooks={'request': [lambda r: print(r.method, r.url)]})), with DefaultHttpx2Client imported from openai. A request to https://api.router.one/chat/completions (no /v1), or a path that starts with /v1/v1, points at the base URL.

Which Python and Node versions do I need, and why does TLS fail in a slim container?

openai 3.x needs Python 3.10 or newer, and openai 7.x needs Node.js 22 or newer. Since 3.0.0 the Python SDK uses HTTPX2, which checks TLS certificates against the operating system's trust store, and certifi is no longer installed. A minimal Docker image without CA certificates, or a network behind a TLS-inspecting corporate proxy, can therefore fail with a certificate error when connecting to https://api.router.one. Install the CA certificates in the image, point SSL_CERT_FILE or SSL_CERT_DIR at your bundle, or pass an ssl.SSLContext with OpenAI(http_client=DefaultHttpx2Client(verify=ssl_context)). The DefaultHttpxClient name still works and now builds an HTTPX2 client; code that passes httpx.Client, httpx.Timeout or httpx transport objects moves to their httpx2 counterparts.

Which Go and Java versions do I need?

openai-go v3.45.0 and later need Go 1.25 or newer; its README says to pin v3.44.0 to stay on Go 1.22 to 1.24. openai-java runs on Java 8 or later. Both read OPENAI_BASE_URL and OPENAI_API_KEY when the client gets no values in code, and neither reads OPENAI_API_BASE.

How do I list the model IDs I can call?

client.models.list() calls GET /v1/models and returns the current catalog's model IDs, image models included; iterate over it in Python, or use for await in Node. The /models page and https://router.one/models.md list the same IDs with context window, endpoints and capabilities. Pass the ID exactly as listed, since the SDK does not check it.

Which models can OpenAI SDK use through the gateway?

Choose a current catalog model that supports both the endpoint and the features OpenAI SDK uses. Check /models and the model detail page for the exact ID, current rates, and capabilities; a family name such as GPT or Claude is not a compatibility guarantee. A client that lists a model has only read the ID, from its own configuration or from GET /v1/models; verify an actual request too.

Models are listed, but requests fail with 400 or 404. What should I check?

Record the actual request path and error message, then check the exact model ID. A 400 can indicate invalid parameters, unsupported tools, or a model/endpoint mismatch; a 404 can indicate an incorrect path or missing resource, so it does not by itself establish that a model was retired. If the error says must be called via, use the named endpoint or select a model supported on the current endpoint. Do not add or remove /v1 or /chat/completions across all clients indiscriminately.

Does this work from Mainland China?

Yes. The gateway is reachable from Mainland China without a VPN, and the configuration is identical to the global setup.

How do I debug a 401/402/403/429?

Start from what your client received: the status, the error body and the X-Request-ID header. A 401 (missing, unknown, revoked or expired key), a 402 (wallet balance, or the key's maxSpend cap) and a 429 from your key's or account's rate limits are refused before any model is called, so they never appear in Dashboard → Logs — nor does a 400 or 404 for a wrong model ID or endpoint. For 401, check that the full key is sent and still active in Dashboard → API Keys; for 402, top up or raise the key's maxSpend; for a 403, keep the full error body. For 429, back off and retry later; a 429 that does show up in Logs came back from upstream after the gateway's retries. Keep the request_id and follow the error-codes reference.