Get started›SDKs and compatibility
SDKs and compatibility
Use the OpenAI, Anthropic or OpenRouter SDK you already have; change the base URL and the key.
There is no SDK of our own to install. Each route speaks a shape an existing SDK already reads, so you point that SDK at the API and keep your code.
| SDK | Base URL | Key |
|---|---|---|
| OpenAI (Python, JavaScript and the rest) | https://api.vultrinference.com/v1 |
api_key |
| Anthropic (Python, JavaScript), Claude Code | https://api.vultrinference.com (the SDK adds /v1) |
api_key, sent as x-api-key |
OpenRouter (@openrouter/sdk, openrouter for Python) |
https://api.vultrinference.com/v1 |
api_key |
OpenAI's SDK
import os
from openai import OpenAI
# The only change from OpenAI's own API: the base URL and the key
client = OpenAI(base_url="https://api.vultrinference.com/v1", api_key=os.environ["VULTR_INFERENCE_API_KEY"])
chat = client.chat.completions.create(
model="deepseek-v4.1-flash",
messages=[{"role": "user", "content": "Name three prime numbers."}],
)
print(chat.choices[0].message.content)
embedding = client.embeddings.create(model="qwen3-embedding-4b", input="Vultr Serverless Inference")
print(len(embedding.data[0].embedding), "dimensions")import OpenAI from "openai";
// The only change from OpenAI's own API: the base URL and the key
const client = new OpenAI({ baseURL: "https://api.vultrinference.com/v1", apiKey: process.env.VULTR_INFERENCE_API_KEY });
const chat = await client.chat.completions.create({
model: "deepseek-v4.1-flash",
messages: [{ role: "user", content: "Name three prime numbers." }],
});
console.log(chat.choices[0].message.content);
const embedding = await client.embeddings.create({ model: "qwen3-embedding-4b", input: "Vultr Serverless Inference" });
console.log(embedding.data[0].embedding.length, "dimensions");Anthropic's SDK
import os
from anthropic import Anthropic
# No /v1 here: Anthropic's SDK adds /v1/messages itself
client = Anthropic(base_url="https://api.vultrinference.com", api_key=os.environ["VULTR_INFERENCE_API_KEY"])
message = client.messages.create(
model="deepseek-v4.1-flash",
max_tokens=1024,
messages=[{"role": "user", "content": "Name three prime numbers."}],
)
print(message.content[-1].text)import Anthropic from "@anthropic-ai/sdk";
// No /v1 here: Anthropic's SDK adds /v1/messages itself
const client = new Anthropic({ baseURL: "https://api.vultrinference.com", apiKey: process.env.VULTR_INFERENCE_API_KEY });
const message = await client.messages.create({
model: "deepseek-v4.1-flash",
max_tokens: 1024,
messages: [{ role: "user", content: "Name three prime numbers." }],
});
const last = message.content[message.content.length - 1];
console.log(last.type === "text" ? last.text : last);What each route speaks
| Route | Shape | SDK calls |
|---|---|---|
POST /v1/chat/completions |
OpenRouter's chat completion, which is OpenAI's with more fields | OpenAI chat., OpenRouter chat.send |
POST /v1/responses |
OpenAI's Responses API | OpenAI responses.create, OpenRouter responses.send |
POST /v1/messages |
Anthropic's Messages API, errors included | Anthropic messages.create, Claude Code |
GET /v1/models |
Our model list; OpenRouter's or Anthropic's shape when the client asks for it | OpenAI and OpenRouter models.list |
POST /v1/images/generations, POST /v1/images/edits |
OpenAI's images API (a link by default) | OpenAI images.generate, images.edit |
POST /v1/images |
OpenRouter's images API (base64 by default) | OpenRouter images.generate |
POST /v1/videos |
OpenRouter's video API, with OpenAI's field names | OpenAI videos., OpenRouter videoGeneration |
POST /v1/audio/speech |
OpenAI's speech API, with OpenRouter's fields | OpenAI audio.speech.create, OpenRouter tts.createSpeech |
POST /v1/audio/transcriptions |
OpenAI's (a file upload) or OpenRouter's (JSON) | OpenAI audio., OpenRouter stt. |
POST /v1/embeddings |
OpenAI's embeddings, with OpenRouter's input_type |
OpenAI embeddings.create, OpenRouter embeddings.generate |
POST /v1/rerank |
OpenRouter's rerank, compatible with Cohere's v2 | OpenRouter rerank.rerank |
POST /v1/systemone |
TypeSafe's System One API | TypeSafe's SDKs |
/v1/files |
OpenAI's Files API, for documents | OpenAI files.create, files.retrieve, files.list, files.delete |
POST /v1/music, /v1/vector_store |
Our own | Plain HTTP |
A field the SDK does not know goes in its extra-body option (extra_body in OpenAI's Python SDK), as Reasoning shows.
Where the API differs
Each route keeps its SDK's shape. These are the places it behaves differently from the original:
| Route | What differs |
|---|---|
| Chat completions | One answer shape whichever engine serves the model (below). No system prompt is added, and a final assistant message is continued rather than answered. |
| Responses | Nothing is stored, so send the whole conversation in input each time. Function, custom and namespace tools work; hosted tools (web search, file search, code interpreter) do not. |
| Messages | model and max_tokens are required. There is no prompt caching and no token counting route. The models are open models, not Claude (Claude Code). |
| Files | Documents only, read once into text. A file expires after 30 days and cannot be downloaded again. |
| Videos | OpenAI's SDKs see queued for pending, and a cancelled or expired job as failed. Webhooks always carry the default shape. |
| Every route | OpenRouter's routing fields (provider, models, route and the like) are accepted and ignored: there is nothing to route between. |
One chat shape
Whichever engine serves the model, a chat completion comes back the same way. A real one, with created, system_fingerprint and logprobs left out:
A chat completion
{
"id": "chatcmpl-00e333f8-d237-4c25-910f-8171becb0de9",
"object": "chat.completion",
"model": "deepseek-v4.1-flash",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "No, 221 is not prime because it equals 13 × 17.",
"reasoning": "We need answer one sentence. Need determine 221 prime? 221 = 13*17? 13*17=221 yes composite. Answer one sentence."
},
"finish_reason": "stop",
"native_finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 38,
"completion_tokens": 51,
"total_tokens": 89,
"prompt_tokens_details": {
"cached_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 33
}
}
}- The thinking is always
reasoning, neverreasoning_content. finish_reasonis one ofstop,length,tool_calls,content_filterorerror. The engine's own word for it is innative_finish_reason.usagealways has both detail objects, so code can read them without checking.- A stream ends with a usage chunk whether or not you ask for one (Streaming).
Endpoints