---
title: "OpenAI SDK"
description: "The official OpenAI SDKs for Python and TypeScript call pirouter with only the base URL and API key changed, on both Chat Completions and Responses. This page covers installing, configuring and verifying the SDK, and what to do about common errors."
canonical: "https://pirouter.ai/docs/integrations/openai-sdk"
last-updated: "2026-10-17"
lang: "en"
source: "content/docs/en/integrations/openai-sdk.mdx"
---

# OpenAI SDK

The official OpenAI SDKs for Python and TypeScript call pirouter with only the base URL and API key changed, on both Chat Completions and Responses. This page covers installing, configuring and verifying the SDK, and what to do about common errors.

The official OpenAI SDKs need no plugin to call pirouter: when you create the client, set the base URL to pirouter's address and the key to a pirouter key. The rest of your code stays as it is.

## Prerequisites

- A pirouter API key. If you do not have one, create it with the [quickstart](https://pirouter.ai/docs/quickstart.md) and store it in an environment variable:

```bash
export PIROUTER_API_KEY="sk-pi-..."
```

- Python 3.10 or later, or Node.js 22 or later.
- The SDK:

Python:

```bash
pip install openai
```

TypeScript:

```bash
pnpm add openai
```

The TypeScript samples use top-level `await`: save the file as `.mjs`, or set `"type": "module"` in `package.json`.

## Configure

Pass two settings when you create the client: `base_url` (`baseURL` in TypeScript) is `https://api.pirouter.ai/v1`, and `api_key` (`apiKey` in TypeScript) is your pirouter key. The SDK sends the key in the `Authorization: Bearer` header, which is where the gateway reads it.

Write the full `vendor/model` ID. For the models you can use, see the OpenAI column of the protocol table in [Models and protocols](https://pirouter.ai/docs/models.md#which-protocols-each-model-accepts): apart from image models, models of other vendors are served over the OpenAI protocol too.

### Chat Completions

Python:

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.pirouter.ai/v1",
    api_key=os.environ["PIROUTER_API_KEY"],
)

completion = client.chat.completions.create(
    model="openai/gpt-6-sol",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(completion.choices[0].message.content)
```

TypeScript:

```ts
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.pirouter.ai/v1',
  apiKey: process.env.PIROUTER_API_KEY,
})

const completion = await client.chat.completions.create({
  model: 'openai/gpt-6-sol',
  messages: [{ role: 'user', content: 'Hello!' }],
})
console.log(completion.choices[0].message.content)
```

### Responses

Python:

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.pirouter.ai/v1",
    api_key=os.environ["PIROUTER_API_KEY"],
)

response = client.responses.create(
    model="openai/gpt-6-sol",
    input="Hello!",
)
print(response.output_text)
```

TypeScript:

```ts
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.pirouter.ai/v1',
  apiKey: process.env.PIROUTER_API_KEY,
})

const response = await client.responses.create({
  model: 'openai/gpt-6-sol',
  input: 'Hello!',
})
console.log(response.output_text)
```

The gateway serves both: `POST /v1/chat/completions` and `POST /v1/responses`.

### Use environment variables

A client created without arguments reads the `OPENAI_BASE_URL` and `OPENAI_API_KEY` environment variables. Set them once, then write `OpenAI()` in your code:

`~/.zshrc`:

```bash
export OPENAI_BASE_URL="https://api.pirouter.ai/v1"
export OPENAI_API_KEY="$PIROUTER_API_KEY"
```

### Stream the response

For Chat Completions, add `stream=True` (`stream: true` in TypeScript) and read `choices[0].delta.content` from each chunk:

Python:

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.pirouter.ai/v1",
    api_key=os.environ["PIROUTER_API_KEY"],
)

stream = client.chat.completions.create(
    model="openai/gpt-6-sol",
    messages=[{"role": "user", "content": "Hello!"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()
```

TypeScript:

```ts
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.pirouter.ai/v1',
  apiKey: process.env.PIROUTER_API_KEY,
})

const stream = await client.chat.completions.create({
  model: 'openai/gpt-6-sol',
  messages: [{ role: 'user', content: 'Hello!' }],
  stream: true,
})
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
}
console.log()
```

For Responses, add `stream` the same way; the text arrives in `response.output_text.delta` events:

Python:

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.pirouter.ai/v1",
    api_key=os.environ["PIROUTER_API_KEY"],
)

stream = client.responses.create(
    model="openai/gpt-6-sol",
    input="Hello!",
    stream=True,
)
for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)
print()
```

TypeScript:

```ts
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.pirouter.ai/v1',
  apiKey: process.env.PIROUTER_API_KEY,
})

const stream = await client.responses.create({
  model: 'openai/gpt-6-sol',
  input: 'Hello!',
  stream: true,
})
for await (const event of stream) {
  if (event.type === 'response.output_text.delta') process.stdout.write(event.delta)
}
console.log()
```

You should see the reply printed piece by piece.

## Verify

Save the Chat Completions sample above as `main.py` or `main.mjs`, then run it:

Python:

```bash
python main.py
```

TypeScript:

```bash
node main.mjs
```

You should see a one-line reply from the model.

Open **Logs** in the console. A few seconds after the request, it appears in the list with `/v1/chat/completions` under **Endpoint**. Every response carries an `X-Pi-Trace-Id` header, the same value as the **Trace ID** in the log details.

## Troubleshooting

### 401 invalid\_api\_key

A `401` with the error code `invalid_api_key` and the `message` "You didn't provide an API key. Provide it in an Authorization header using Bearer auth (Authorization: Bearer YOUR\_KEY)." means the request carried no key, or carried something other than a pirouter key starting with `sk-pi-`.

Check that `PIROUTER_API_KEY` is set in the terminal that runs the code. With the environment variable setup, `OPENAI_API_KEY` must hold the pirouter key, not an OpenAI one. For other authentication failures, see [Authentication](https://pirouter.ai/docs/authentication.md#when-authentication-fails).

### 400 model is required

A `400` with the error code `invalid_request` and the `message` `model is required` usually means the base URL is `https://api.pirouter.ai`, without `/v1`. The request path is then not an endpoint the gateway serves, and the gateway cannot find a model in it. Set the base URL to `https://api.pirouter.ai/v1`.

### 422 validation\_failed

A `422` with the error code `validation_failed` and a `message` starting "This model is not served on the endpoint you called." means the model is not served on the endpoint you called, for example an image model on Chat Completions. The rest of the `message` lists the endpoints it is served on: call one of those, or pick a model that generates text over the OpenAI protocol.

## Not verified yet

- The gateway serves only the create call of Responses, `POST /v1/responses`. The calls that read, delete or cancel a response (such as `GET /v1/responses/{id}`) are not available today; they return `400` with `model is required`.
- `POST /v1/embeddings`, which `client.embeddings` calls, is not available today and returns `400` as well.
