OpenAI SDK
POST /v1/chat/completions is OpenAI’s Chat Completions API, so the official OpenAI SDK (and any tool built on it) works by changing its base URL and using a VectorAxis virtual key as its API key. The key decides the provider: the same code reaches OpenAI, Anthropic, Google, Groq and every other provider, with VectorAxis translating to each one’s own format.
from openai import OpenAI
client = OpenAI(
base_url="https://api.vectoraxis.ai/v1",
api_key="vk-1a2b3c4d5e6f7a8b.Xk3pQ9vN2mR7tY1wZ8bC4dF6gH0jK5lM3nP7qS9uV2w", # your VectorAxis API key
default_headers={"x-cache-mode": "simple"}, # optional gateway features
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.vectoraxis.ai/v1',
apiKey: process.env.VECTORAXIS_KEY, // vk-...
});
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Hello!' }],
});Every VectorAxis feature (caching, retries, fallbacks, guardrails, routing configs) is a request header: set it once with default_headers / defaultHeaders, or per call with extra_headers. The body stays pure OpenAI format.
| Feature | Notes |
|---|---|
| Streaming | stream=True returns OpenAI-format chunks ending in [DONE]. The gateway always asks the provider for usage, so the last chunk carries token counts (with an empty choices list). An error after the first chunk (an output guardrail block, a provider failing mid-answer) arrives as a chunk with an error object, which the SDK raises. Streams use the cache and are retried or failed over until their first token: see Streaming. |
| Tools | tools (with strict), tool_choice, parallel_tool_calls and tool-role results, on every provider; for Claude they are translated to Anthropic’s tool_use / tool_result. |
| Images, PDFs and audio | Content parts image_url, file (file_data, or a vxf-… id from Files) and input_audio go to the provider as sent. Claude takes images and PDFs; audio on a Claude model is a 400. |
| Structured output | response_format json_schema, sent to Claude as its output_config.format. |
| Reasoning | reasoning_effort (Claude: output_config.effort), and Claude’s thinking through extra_body. Claude’s thinking comes back on the message as thinking_blocks; send them back on the assistant message to continue a thinking turn. |
| Prompt caching | On Claude models, cache_control on a content part or a tool, or at the top level through extra_body. Below. |
| Stop sequences | stop. On Claude the choice also carries the sequence that matched as stop_sequence. |
| User | user is recorded in Logs. |
Create an Anthropic virtual key and name a Claude model; nothing else changes. Anthropic-specific options ride in extra_body:
claude = OpenAI(base_url="https://api.vectoraxis.ai/v1", api_key=ANTHROPIC_VIRTUAL_KEY)
response = claude.chat.completions.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{"role": "user", "content": "Plan a three-step migration."}],
extra_body={"thinking": {"type": "enabled", "budget_tokens": 2048}},
)
message = response.choices[0].message
print(message.content)
print(message.model_extra.get("thinking_blocks")) # signed thinking, to send back next turnIf your code is written for Anthropic’s own API, use the Claude SDK instead: it adds token counting and Message Batches.
This is the provider’s own prompt cache, separate from the gateway’s response cache. On a Claude model a cache_control breakpoint goes on the content part (or tool) it should end at:
response = claude.chat.completions.create(
model="claude-haiku-4-5",
messages=[
{"role": "system", "content": [{
"type": "text",
"text": LONG_REFERENCE_TEXT,
"cache_control": {"type": "ephemeral"},
}]},
{"role": "user", "content": "Summarise section 3."},
],
)
usage = response.usage
print(usage.prompt_tokens_details.cached_tokens) # read from the cache
print(usage.model_extra.get("cache_creation_input_tokens")) # written to it- prompt_tokens includes the cached tokens, as OpenAI reports it; the part read from the cache is prompt_tokens_details.cached_tokens, and writes are cache_creation_input_tokens.
- OpenAI caches long prompts automatically, with no breakpoint; its cached_tokens are reported the same way.
- Cached tokens are charged at the model’s cache rates, not as fresh input.
- cache_control is removed before the request reaches OpenAI and other OpenAI-format providers, which would reject it, and passed on to OpenRouter, which forwards it to the Claude models it serves.
Errors raise the SDK’s usual exceptions with the HTTP status. A provider error keeps the provider’s own status and message. Two statuses come from the gateway itself:
| Status | Meaning |
|---|---|
| 422 | A guardrail or the tool-result scan blocked the request: x-guardrail-status: BLOCK, plus x-tool-scan-status: BLOCK for the scan. |
| 503 | A provider 502 or 504 is returned as 503 (the original status in X-Upstream-Status), so it passes the edge network intact; the SDK retries it like any 5xx. |
The same client reaches audio, images, files, batches and the Responses API: see Documents & media. client.decisions.create(...) reaches OpenAI’s Decisions API (/v1/decisions): see OpenAI Decisions. Managed prompts run as a chat completion through /v1/prompts/{slug}/completions (Prompt management).