Docs menu

OpenAI SDK

Wire: OpenAI. Base URL: https://sator-api.princep.org/v1. State: verified.

Install#

bash
pip install openai        # Python
npm install openai        # TypeScript / JavaScript

Configure#

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 as input each call and never previous_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 retries 429 and 5xx on its own and honours retry-after. See Errors.

Troubleshooting#

  • 404 on every callbase_url is missing its /v1.
  • OPENAI_API_BASE had no effect — the SDK reads OPENAI_BASE_URL. (OPENAI_API_BASE is Aider's name for the same thing.)
  • AuthenticationError — the key is wrong or revoked; check the dashboard.