---
url: https://docs.freetheai.org/sdks/openai-python.md
description: >-
  Use the official OpenAI Python SDK with FreeTheAI - set base_url and your key,
  then use chat completions, streaming, tools, Responses, async, and error
  handling.
---

# OpenAI Python SDK

The official [`openai`](https://pypi.org/project/openai/) package works with FreeTheAI as is. Only the base URL and the key change.

## Install and configure

```bash
pip install openai
export FTAI_KEY="ftai_your_key_here"
```

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.freetheai.org/v1",
    api_key=os.environ["FTAI_KEY"],
)
```

You can also set the environment variables the SDK reads by default, `OPENAI_BASE_URL=https://api.freetheai.org/v1` and `OPENAI_API_KEY=ftai_...`, and create the client with `OpenAI()`.

## Chat completion

```python
reply = client.chat.completions.create(
    model="fta/zai/glm-5.3",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain recursion in one paragraph."},
    ],
)
print(reply.choices[0].message.content)
print(reply.usage)
```

## Streaming

```python
stream = client.chat.completions.create(
    model="fta/kimi/k3",
    messages=[{"role": "user", "content": "Plan a three-step study schedule for Go."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
```

## Async

```python
import asyncio
import os
from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="https://api.freetheai.org/v1", api_key=os.environ["FTAI_KEY"])

async def main():
    reply = await client.chat.completions.create(
        model="fta/zai/glm-5.3",
        messages=[{"role": "user", "content": "Say hello in five words."}],
    )
    print(reply.choices[0].message.content)

asyncio.run(main())
```

Free accounts run one request at a time, so send requests one after another rather than with `asyncio.gather`.

## Responses API

```python
response = client.responses.create(
    model="fta/zai/glm-5.3",
    input="Write a haiku about the sea.",
)
print(response.output_text)
```

## Tool calls

See [Tool calls](/concepts/tool-calls) for a complete loop.

## Fields the SDK does not know

Pass FreeTheAI-specific or model-specific fields with `extra_body`:

```python
reply = client.chat.completions.create(
    model="fta/zai/glm-5.3",
    messages=[{"role": "user", "content": "Quick answer: 17 * 23?"}],
    extra_body={"thinking": {"type": "disabled"}},
)
```

## Errors

The SDK raises typed exceptions. `err.body` holds FreeTheAI's error object, and its `message` is safe to show to people.

```python
import openai

def error_message(err: openai.APIStatusError) -> str:
    body = err.body if isinstance(err.body, dict) else {}
    return body.get("message") or str(err)

try:
    reply = client.chat.completions.create(
        model="fta/zai/glm-5.3",
        messages=[{"role": "user", "content": "Hi"}],
    )
except openai.AuthenticationError as err:      # 401
    print("Check your key:", error_message(err))
except openai.PermissionDeniedError as err:    # 403, for example the daily check-in
    print("Not allowed:", error_message(err))
except openai.NotFoundError as err:            # 404, unknown model
    print("Unknown model:", error_message(err))
except openai.RateLimitError as err:           # 429
    print("Limit reached:", error_message(err))
except openai.APIStatusError as err:           # 400, 413, 502, 503, ...
    print(err.status_code, error_message(err), err.response.headers.get("x-error-id"))
```

The SDK retries some errors on its own (2 retries by default). Set `max_retries` and `timeout` on the client to change that. Every message and its fix: [Errors](/errors).
