OpenAI SDK
Wire: OpenAI. Base URL: https://sator-api.princep.org/v1. State: verified.
Install#
bash
pip install openai # Python
npm install openai # TypeScript / JavaScriptConfigure#
Pass the base URL and key to the client, or set the two environment variables the SDK reads on its own — note that the SDK reads OPENAI_BASE_URL, not OPENAI_API_BASE:
bash
export OPENAI_BASE_URL=https://sator-api.princep.org/v1
export OPENAI_API_KEY=sk-sator-v1-...Python#
python
from openai import OpenAI
client = OpenAI(base_url="https://sator-api.princep.org/v1", api_key="sk-sator-v1-...")
completion = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": "Say hi"}],
)
print(completion.choices[0].message.content)
print(completion.usage)Streaming:
python
with client.chat.completions.stream(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": "Count to five"}],
) as stream:
for event in stream:
if event.type == "content.delta":
print(event.delta, end="", flush=True)TypeScript#
ts
import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://sator-api.princep.org/v1', apiKey: process.env.SATOR_API_KEY });
const completion = await client.chat.completions.create({
model: 'deepseek-v4-flash',
messages: [{ role: 'user', content: 'Say hi' }],
});
console.log(completion.choices[0].message.content, completion.usage);Streaming:
ts
const stream = await client.chat.completions.create({
model: 'deepseek-v4-flash',
messages: [{ role: 'user', content: 'Count to five' }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}The final chunk carries usage whether or not you set stream_options.include_usage.
Verify#
Either sample prints a short reply and a usage object with non-zero prompt_tokens and completion_tokens. The request appears in your dashboard.
What works, what does not#
client.chat.completions— yes, with tools,response_format, images as base64 data URLs, and streaming. See Tool calling.client.models.list()— yes, no key needed.client.responses— yes, stateless: pass the whole conversation asinputeach call and neverprevious_response_id. See Responses.client.embeddings— no.- The SDK's typed errors (
AuthenticationError,RateLimitError, and so on) map onto Sator's statuses directly; the SDK retries429and5xxon its own and honoursretry-after. See Errors.
Troubleshooting#
- 404 on every call —
base_urlis missing its/v1. OPENAI_API_BASEhad no effect — the SDK readsOPENAI_BASE_URL. (OPENAI_API_BASEis Aider's name for the same thing.)AuthenticationError— the key is wrong or revoked; check the dashboard.